訂單狀態(tài)流轉是交易系統(tǒng)的最為核心的功能,復雜的訂單系統(tǒng)會存在狀態(tài)多、鏈路長、邏輯復雜的特點,針對不同的商品、業(yè)務、發(fā)貨方式還存在多場景、多類型、多業(yè)務維度等業(yè)務特性。在保證訂單狀態(tài)流轉穩(wěn)定性的前提下、可擴展性和可維護性由是需要重點關注和解決的問題。
以公司目前的訂單系統(tǒng)為例,訂單狀態(tài)有待支付、支付成功、訂單取消、訂單關閉等;訂單類型有積分訂單、商城訂單、充值訂單、禮品卡訂單、付費會員訂單等幾種類型;業(yè)務場景普通訂單、供應商訂單等場景。
當訂單狀態(tài)、類型、場景、以及其他一些維度組合時,每一種組合都可能會有不同的處理邏輯、也可能會存在共性的業(yè)務邏輯,處理這種"多狀態(tài)+多類型+多場景+多維度"的復雜訂單狀態(tài)流轉業(yè)務,又要保證整個系統(tǒng)的可擴展性和可維護性。
主要的設計思路為采用模版、策略方法隔離業(yè)務復用功能組件,縱向主要從業(yè)務隔離和流程編排的角度解決問題、而橫向主要從邏輯復用和業(yè)務擴展的角度解決問題。
1 縱向解決業(yè)務隔離和流程編排
通常處理一個多狀態(tài)或者多維度的業(yè)務邏輯,都會采用狀態(tài)模式或者策略模式來解決,抽象一個基礎邏輯接口、每一個狀態(tài)或者類型都實現(xiàn)該接口,業(yè)務處理時根據不同的狀態(tài)或者類型調用對應的業(yè)務實現(xiàn),以到達邏輯相互獨立互不干擾、代碼隔離的目的。
單一狀態(tài)或類型可以通過上面的方法解決,針對"多狀態(tài)+多類型+多場景+多維度"這種組合業(yè)務也可以采用這種模式或思路來解決。首先通過一個注解@OrderPorcessor將不同的維度予以組合、開發(fā)出多個對應的具體實現(xiàn)類,在系統(tǒng)運行階段,通過判斷上下文來動態(tài)選擇具體使用哪一個實現(xiàn)類執(zhí)行。@OrderPorcessor中分別定義state代表當前處理器要處理的狀態(tài),bizCode和scene分別代表業(yè)務類型和場景,這兩個字段留給業(yè)務進行擴展,比如可以用bizCode代表產品或訂單類型、scene代表業(yè)務形態(tài)。
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Componentpublic @interface OrderProcessor { /** * 指定狀態(tài),state不能同時存在 */ OrderStatus[] state(); /** * 業(yè)務 */ OrderType[] orderType() default {}; /** * 場景 */ Scene[] scene() default {}; /** * 訂單操作事件 */ OrderEventType event(); }
public BaseResult<T> action(OrderContext<C> context) throws Exception { //校驗 BaseResult<T> checkResult = this.check(context); if (!checkResult.isSuccess()) { return checkResult; } //準備數(shù)據 this.prepare(context); //獲取當前狀態(tài)處理器處理完畢后,所處于的下一個狀態(tài) OrderStatus nextState = this.getNextState(context); //狀態(tài)動作方法,主要狀態(tài)遷移邏輯 BaseResult<T> actionResult = this.action(nextState, context); if (!actionResult.isSuccess()) { return actionResult; } //狀態(tài)數(shù)據持久化 BaseResult<T> result = this.save(nextState, context); // 狀態(tài)遷移成功,持久化后執(zhí)行的后續(xù)處理 if (result.isSuccess()) { TransactionUtil.doAfterCommitTransaction(t -> this.after(context)); } return result; }
2 橫向解決邏輯復用和實現(xiàn)業(yè)務擴展
在真正編碼的時候會發(fā)現(xiàn)不同類型不同維度對于同一個狀態(tài)的流程處理過程,有時多個處理邏輯中的一部分流程一樣的或者是相似的,甚至有些時候多個類型間的處理邏輯大部分是相同的而差異是小部分,比如下單流程的處理邏輯基本邏輯都差不多(鎖、校驗狀態(tài)&參數(shù)、處理、保存、同步訂單到es、發(fā)送消息、設置定時任務等),而付vip、svip、分銷價格的不同,充值和購物訂單流程、庫存管理、發(fā)貨處理形式等個別流程的差異。對于這種情況就是要實現(xiàn)在縱向解決業(yè)務隔離和流程編排的基礎上,需要支持小部分邏輯或代碼段的復用、或者大部分流程的復用,減少重復開發(fā)。
具體的實現(xiàn)方式一是通過繼承重寫的方式,先實現(xiàn)一個默認的處理器,把所有的標準處理流程和可擴展點進行封裝實現(xiàn)、其他處理器進行繼承、覆寫、替換就好;實現(xiàn)方式二是通過在內容上下文初始化前后、在功能業(yè)務處理前后以及持久化前后植入拓展點,通過@OrderProcessor實現(xiàn)的插件,進行匹配篩選需要具體處理器執(zhí)行的插件并執(zhí)行。
public BaseResult<T> action(OrderContext<C> context) throws Exception { List<OrderPostProcessor> supportProcessor = defaultPluginRegistry.getSupportProcessor(context); log.info("獲取插件,插件數(shù)量:{},插件列表:{}", supportProcessor.size(), supportProcessor); List<OrderPreparePostProcessor> orderPreparePostProcessors = supportProcessor .stream() .filter(item -> OrderPreparePostProcessor.class.isAssignableFrom(item.getClass()) && item.isOpen(context.getOrderProcessParam().getScene())) .map(item -> (OrderPreparePostProcessor) item) .collect(Collectors.toList()); if (CollUtil.isNotEmpty(orderPreparePostProcessors)) { orderPreparePostProcessors.forEach(item -> item.orderPostProcessBeforePrepare(context)); } BaseResult<T> checkResult = this.check(context); if (!checkResult.isSuccess()) { return checkResult; } this.prepare(context); ......... }