SpringBoot - 用責任鏈模式實現業務編排

文章目錄

  • 前因
  • 責任鏈:像工作臺一樣組織代碼
  • Code
    • SEQ
    • 3.1 定義處理器規范
    • 3.2 實現具體處理器
    • 3.3 共享上下文
    • 3.4 組裝責任鏈
  • 適用場景
  • 優勢

在這里插入圖片描述


前因

2000多行的業務邏輯里,各種校驗規則、促銷計算、庫存操作像意大利面條一樣纏繞在一起。最要命的是這樣的代碼結構:

public void createOrder(OrderRequest request) {// 參數校驗if(request.getUserId() == null){throw new Exception("用戶ID不能為空");}if(request.getItems().isEmpty()){throw new Exception("商品不能為空");}// 庫存檢查for(Item item : request.getItems()){if(!inventoryService.checkStock(item)){throw new Exception("庫存不足");}}// 優惠計算if(request.getCouponId() != null){// 復雜的優惠計算邏輯...}// 風控檢查if(riskService.checkRisk(request)){throw new Exception("風控攔截");}// 保存訂單// ...
}

每次需求變更都像在雷區跳舞,稍有不慎就會引發連鎖反應。新來的小伙伴看著滿屏的if-else,戰戰兢兢地問:“咱們能不能換個寫法?”


責任鏈:像工作臺一樣組織代碼

這時候就該責任鏈模式登場了!這個設計模式的核心思想是:把每個處理步驟拆成獨立的處理器,像流水線一樣連接起來

想象一下快遞分揀系統:

  1. 包裹先過安檢機(校驗處理器)
  2. 然后到分揀區(路由處理器)
  3. 接著稱重計費(價格處理器)
  4. 最后裝車發貨(持久化處理器)

每個環節只關心自己的職責,處理完就交給下一個環節。這樣無論是要調整環節順序,還是增加新的處理環節,都變得非常靈活。

Code

SEQ

客戶端 OrderController OrderChainManager ValidationHandler StockCheckHandler DiscountHandler OrderContext POST /orders (OrderRequest) 創建OrderContext executeChain(context) 獲取所有處理器并排序 handle(context) 參數校驗 return false return true alt [校驗失敗] [校驗成功] handle(context) 庫存檢查 return false return true alt [庫存不足] [庫存充足] handle(context) 優惠計算 return true 繼續后續處理器... alt [庫存充足] alt [校驗成功] 返回OrderResult 200 OK (OrderResult) 400 BadRequest (ErrorInfo) alt [全部成功] [任何失敗] 客戶端 OrderController OrderChainManager ValidationHandler StockCheckHandler DiscountHandler OrderContext

3.1 定義處理器規范

package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;/*** 處理器接口定義*/
public interface OrderHandler {/*** 處理 : 返回true ,繼續處理,返回false,終止處理** @param orderContext* @return*/boolean handle(OrderContext orderContext);/*** 獲取處理順序** @return*/int getOrder();
}

3.2 實現具體處理器

參數校驗處理器

 package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;@Component
@Order(100)
public class ValidationHandler implements OrderHandler {/*** 處理訂單請求的函數* 該函數主要用于驗證訂單請求中的必要信息是否完整* 如果驗證失敗,它會設置上下文結果并返回false** @param context 訂單上下文,包含訂單請求和結果* @return 如果驗證通過,返回true;否則返回false*/@Overridepublic boolean handle(OrderContext context) {// 獲取訂單請求OrderRequest request = context.getRequest();// 驗證用戶ID是否為空if (StringUtils.isEmpty(request.getUserId())) {// 如果用戶ID為空,設置驗證失敗的結果并返回context.setResult(OrderResult.fail("VALIDATION_ERROR", "用戶ID不能為空"));return false;}// 驗證訂單商品列表是否為空if (CollectionUtils.isEmpty(request.getItems())) {// 如果訂單商品列表為空,設置驗證失敗的結果并返回context.setResult(OrderResult.fail("VALIDATION_ERROR", "訂單商品不能為空"));return false;}// 如果所有驗證都通過,返回truereturn true;}/*** 獲取當前對象的順序值** 順序值用于確定對象處理的優先級或顯示順序* 值越小 優先級越高** @return 返回順序值100,表示當前對象的默認順序位置*/@Overridepublic int getOrder() {return 100;}
}

庫存檢查處理器

 package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import com.artisan.chain.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 庫存檢查處理器,用于在訂單處理流程中檢查商品庫存*/
@Component
@Order(200)
public class StockCheckHandler implements OrderHandler{// 注入庫存服務,用于檢查商品庫存情況@Autowiredprivate InventoryService inventoryService;/*** 處理訂單中的庫存檢查邏輯* 遍歷訂單中的每項商品,檢查庫存是否充足* 如果任何商品的庫存不足,訂單處理失敗,并更新訂單上下文的結果** @param context 訂單上下文,包含訂單請求和處理結果* @return 如果所有商品庫存充足,返回true;否則返回false*/@Overridepublic boolean handle(OrderContext context) {// 遍歷訂單中的每個商品項,檢查庫存for (OrderRequest.OrderItem item : context.getRequest().getItems()) {// 如果庫存檢查失敗,更新訂單上下文的結果為庫存錯誤,并返回falseif (!inventoryService.checkStock(item.getSkuId(), item.getQuantity())) {context.setResult(OrderResult.fail("STOCK_ERROR","商品[" + item.getSkuId() + "]庫存不足"));return false;}}// 所有商品庫存充足,返回truereturn true;}/*** 獲取訂單處理的順序* 用于確定在訂單處理流程中執行庫存檢查的順序** @return 訂單處理的順序值*/@Overridepublic int getOrder() {return 200;}
}
package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.List;/*** 定義一個處理折扣的組件*/
@Component
@Order(300)
public class DiscountHandler implements OrderHandler {/*** 處理訂單中的折扣計算** @param context 訂單上下文,包含訂單請求和屬性* @return 總是返回true,表示處理成功*/@Overridepublic boolean handle(OrderContext context) {// 模擬優惠計算double total = calculateTotal(context.getRequest().getItems());double discount = calculateDiscount(total, context.getRequest().getCouponId());// 將最終金額存入上下文中context.getAttributes().put("finalAmount", total - discount);return true;}/*** 計算訂單總金額** @param items 訂單中的商品列表* @return 訂單總金額*/private double calculateTotal(List<OrderRequest.OrderItem> items) {// 模擬價格計算,這里簡化處理,實際應根據商品價格和數量計算return items.stream().mapToDouble(item -> item.getQuantity() * 100.0) // 模擬價格.sum();}/*** 計算折扣金額** @param total    訂單總金額* @param couponId 優惠券ID,如果為空則不應用折扣* @return 折扣金額*/private double calculateDiscount(double total, String couponId) {// 模擬優惠計算,如果有優惠券ID,則應用10%的折扣return couponId != null ? total * 0.1 : 0;}/*** 獲取處理順序** @return 處理順序值*/@Overridepublic int getOrder() {return 300;}
}

3.3 共享上下文

package com.artisan.chain.model;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.HashMap;
import java.util.Map;/*** 上下文對象(共享數據載體)** 訂單上下文類,用于處理訂單請求并生成訂單結果* 它封裝了訂單請求、處理結果以及相關屬性**/@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderContext {/*** 訂單請求對象,包含訂單的相關請求信息*/private OrderRequest request;/*** 訂單處理結果對象,用于存儲訂單處理后的信息*/private OrderResult result = new OrderResult();/*** 訂單相關屬性集合,用于存儲訂單處理過程中需要的臨時信息* 鍵為屬性名稱,值為屬性值*/private Map<String, Object> attributes = new HashMap<>();/*** 構造方法,初始化訂單上下文** @param request 訂單請求對象,不能為空*/public OrderContext(OrderRequest request) {this.request = request;}
}

3.4 組裝責任鏈

 package com.artisan.chain.manager;import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderResult;
import org.springframework.stereotype.Component;import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;/*** 訂單鏈式處理器管理類* 通過鏈式調用多個OrderHandler來處理訂單邏輯* 該類負責構建和管理這些處理訂單的處理器鏈*/
@Component
public  class OrderChainManager {/*** 存儲所有的訂單處理器,按照處理順序排序*/private final List<OrderHandler> handlers;/*** 構造函數,初始化訂單處理器鏈* @param handlers 一個未排序的訂單處理器集合*/public OrderChainManager(List<OrderHandler> handlers) {// 根據每個處理器的順序值進行排序,確保它們按照正確的順序執行this.handlers = handlers.stream().sorted(Comparator.comparingInt(OrderHandler::getOrder)).collect(Collectors.toList());}/*** 執行訂單處理邏輯* 遍歷每個訂單處理器,直到所有處理器都處理完畢或某個處理器決定中斷鏈式處理* @param context 訂單上下文,包含訂單的處理信息和結果* @return 處理后的訂單結果*/public OrderResult execute(OrderContext context) {// 遍歷處理器鏈,如果某個處理器處理失敗(返回false),則中斷鏈式處理for (OrderHandler handler : handlers) {if (!handler.handle(context)) {break;}}// 返回最終的訂單處理結果return context.getResult();}}
package com.artisan.chain.config;import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.manager.OrderChainManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Collections;
import java.util.List;/*** 配置類,用于定義和配置訂單處理鏈相關的Bean*/
@Configuration
public class HandlerConfig {/*** 創建并配置OrderChainManager Bean** @param handlersProvider 一個對象提供者,用于提供訂單處理器列表如果未找到則提供一個空列表* @return 返回一個OrderChainManager實例,用于管理訂單處理鏈*/@Beanpublic OrderChainManager orderChainManager(ObjectProvider<List<OrderHandler>> handlersProvider) {// 初始化OrderChainManager,使用提供的訂單處理器列表,如果沒有提供則使用空列表return new OrderChainManager(handlersProvider.getIfAvailable(Collections::emptyList));}
}
# 測試成功案例
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": "user123","items": [{"skuId": "SKU001", "quantity": 2},{"skuId": "SKU002", "quantity": 1}],"couponId": "COUPON2023"
}'# 測試庫存不足
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": "user123","items": [{"skuId": "SKU001", "quantity": 200}]
}'

適用場景

  • 多步驟流程:訂單創建、審批流、支付流程等

  • 動態業務:需要頻繁調整步驟順序的業務

  • 復雜校驗:多層次、多條件的校驗場景

  • 插件式架構:需要動態加載/卸載功能的系統

優勢

  • 解耦性:每個處理邏輯獨立成Handler,修改單個處理器不影響其他組件
  • 可擴展性:新增業務邏輯只需添加新Handler,無需修改主流程
  • 動態編排:通過配置靈活調整處理器執行順序和啟用狀態
  • 可測試性:每個Handler可單獨進行單元測試
  • 復用性:通用處理器(如日志記錄)可跨多個業務場景復用

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72657.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72657.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72657.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

upload-labs詳解(13-20)文件上傳分析

目錄 upload-labs-env upload-labs-env第十三關 文件包含漏洞 代碼 測試 上傳一個.jpg圖片 上傳一個.png文件 上傳一個.gif圖片 upload-labs-env第十四關 代碼 思路 upload-labs-env第十五關 代碼 思路 upload-labs-env第十六關 代碼 思路 測試 上傳gif格式…

網絡安全通信架構圖

&#x1f345; 點擊文末小卡片 &#xff0c;免費獲取網絡安全全套資料&#xff0c;資料在手&#xff0c;漲薪更快 在安全通信里面我經常聽到的2個東西就是SSL和TLS&#xff0c;這2個有什么區別呢&#xff1f;以及HTTPS是怎么通信的&#xff1f;包括對稱加密、非對稱加密、摘要、…

Java中的String類

目錄 1. String類的重要性 2. 常用方法 2.1 字符串構造 2.2 String對象的比較 2.3 字符串查找 2.4 轉化 2.5 字符串替換 2.6 字符串拆分 2.7 字符串截取 2.8 其他操作方法 2.9 字符串的不可變性 2.10 字符串修改 3. StringBuilder和StringBuffer 3.1 StringBuilde…

深度分頁介紹及優化建議

深度分頁介紹 查詢偏移量過大的場景我們稱為深度分頁&#xff0c;這會導致查詢性能較低&#xff0c;例如&#xff1a; # MySQL 在無法利用索引的情況下跳過1000000條記錄后&#xff0c;再獲取10條記錄 SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10 深度分頁問題的原…

live555推流服務器異常

1.后端異常信息&#xff1a; MultiFramedRTPSink::afterGettingFrame1(): The input frame data was too large for our buffer size (100176). 48899 bytes of trailing data was dropped! Correct this by increasing "OutPacketBuffer::maxSize" to at least m…

每日OJ_牛客_宵暗的妖怪_DP_C++_Java

目錄 牛客_宵暗的妖怪_DP 題目解析 C代碼 Java代碼 牛客_宵暗的妖怪_DP 宵暗的妖怪 描述&#xff1a; 露米婭作為宵暗的妖怪&#xff0c;非常喜歡吞噬黑暗。這天&#xff0c;她來到了一條路上&#xff0c;準備吞噬這條路上的黑暗。這條道路一共被分為n 部分&…

20250306-筆記-精讀class CVRPEnv:step(self, selected)

文章目錄 前言一、if self.time_step<4:控制時間步的遞增判斷是否在配送中心特定時間步的操作更新更新當前節點和已選擇節點列表更新需求和負載更新訪問標記更新負無窮掩碼更新步驟狀態&#xff0c;將更新后的狀態同步到 self.step_state 二、使用步驟總結 前言 class CVRP…

Flowable 基本入門

flowable.7z官方版下載丨最新版下載丨綠色版下載丨APP下載-123云盤 1、Flowable介紹 Flowable是BPMN的一個基于java的軟件實現&#xff0c;不過Flowable不僅僅包括BPMN&#xff0c;還有DMN決策表和CMMN Case管理引擎&#xff0c;并且有自己的用戶管理、微服務API等一系列功能&a…

完全背包-一維數組

52. 攜帶研究材料&#xff08;第七期模擬筆試&#xff09; 題目描述 小明是一位科學家&#xff0c;他需要參加一場重要的國際科學大會&#xff0c;以展示自己的最新研究成果。他需要帶一些研究材料&#xff0c;但是他的行李箱空間有限。這些研究材料包括實驗設備、文獻資料和…

景聯文科技:以專業標注賦能AI未來,驅動智能時代的精準躍遷

在人工智能技術重塑全球產業格局的今天&#xff0c;高質量訓練數據已成為驅動算法進化的核心燃料。作為數據智能服務領域的領軍者&#xff0c;景聯文科技深耕數據標注行業多年&#xff0c;以全棧式數據解決方案為核心&#xff0c;構建起覆蓋數據采集、清洗、標注、質檢及算法調…

洛谷B2074 計算星期幾

B2074 計算星期幾 - 洛谷 代碼區&#xff1a; #include<algorithm> #include<iostream> #include<unordered_map> #include<string> using namespace std; int main() {unordered_map<int, string> m { { 1,"Monday" },{2,"Tue…

協同過濾推薦算法+微信小程序的農產品團購推薦平臺(程序+論文+講解+安裝+調試+售后)

感興趣的可以先收藏起來&#xff0c;還有大家在畢設選題&#xff0c;項目以及論文編寫等相關問題都可以給我留言咨詢&#xff0c;我會一一回復&#xff0c;希望幫助更多的人。 系統介紹 在當今時代&#xff0c;科學技術正以令人矚目的速度迅猛進步&#xff0c;經濟社會也隨之…

十大經典排序算法簡介

一 概述 本文對十大經典排序算法做簡要的總結(按常用分類方式排列),包含核心思想、時間/空間復雜度及特點。 二、比較類排序 1. 冒泡排序 (BUBBLE SORT) 思想:重復交換相鄰逆序元素,像氣泡上浮 復雜度: 時間:O(n^2)(最好情況O(n)) 空間:O(1) 特點:簡單但效率低,穩…

[自然語言處理]pytorch概述--什么是張量(Tensor)和基本操作

pytorch概述 PyTorch 是?個開源的深度學習框架&#xff0c;由 Facebook 的??智能研究團隊開發和維護&#xff0c;于2017年在GitHub上開源&#xff0c;在學術界和?業界都得到了?泛應? pytorch能做什么 GPU加速自動求導常用網絡層 pytorch基礎 量的概念 標量&#xf…

Spring統一格式返回

目錄 一&#xff1a;統一結果返回 1&#xff1a;統一結果返回寫法 2&#xff1a;String類型報錯問題 解決方法 二&#xff1a;統一異常返回 統一異常返回寫法 三&#xff1a;總結 同志們&#xff0c;今天咱來講一講統一格式返回啊&#xff0c;也是好久沒有講過統一格式返…

【無標題】四色拓撲模型與宇宙歷史重構的猜想框架

### 四色拓撲模型與宇宙歷史重構的猜想框架 --- #### **一、理論基礎&#xff1a;四色拓撲與時空全息原理的融合** 1. **宇宙背景信息的拓撲編碼** - **大尺度結構網絡**&#xff1a;將星系團映射為四色頂點&#xff0c;纖維狀暗物質結構作為邊&#xff0c;構建宇宙尺度…

藍橋杯 封閉圖形個數

藍橋杯 封閉圖形個數 題目 鏈接 解答 # 數字個數 n int(input()) # 數字 ls input().split() # 統計數字的圈數 o_nums {} for i, x in enumerate(ls):o_num 0for c in x:if int(c) in [0, 4, 6, 9]:o_num 1elif c 8:o_num 2o_nums[i] o_num # 字典根據圓圈數排序 …

基于javaweb的SpringBoot學生在線考試管理系統設計和實現(源碼+文檔+部署講解)

技術范圍&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬蟲、數據可視化、小程序、安卓app、大數據、物聯網、機器學習等設計與開發。 主要內容&#xff1a;免費功能設計、開題報告、任務書、中期檢查PPT、系統功能實現、代碼編寫、論文編寫和輔導、論…

國產編輯器EverEdit - 超多樣式設置

1 設置-編輯-樣式 1.1 設置說明 1.1.1 折疊樣式 默認為箭頭&#xff0c;折疊樣式選項如下&#xff1a; 箭頭&#xff1a; 矩形和線條 五邊形 圓形圖標 1.1.2 光標樣式 光標用于指示當前用戶輸入位置&#xff0c;光標樣式選項如下&#xff1a; 默認 纖細 字寬 …

Linux - 線程控制

一、線程概念 1&#xff09;線程地址空間 線程與進程共享相同的虛擬地址空間&#xff0c;因此線程在訪問內存時與進程沒有本質的區別。但線程共享和獨占的內存區域有不同的特點&#xff0c;理解這些特性對于正確使用線程至關重要。 1. 線程地址空間的組成 線程的地址空間是…