作者:京東物流 呂順
背景
在物流系統中,接單是信息流的關鍵和重要的一環,每個業務場景都會對應一種標準接單流程,例如銷售出、采購入等等。標準接單包括統一接口定義、統一數據模型、標準接單核心應用職責劃分。而這個標準并不是在接口定義的初期就規劃好的,通常會經歷業務不斷增長而帶來的需求迭代、業務融合、組織架構調整或升級引起的流程優化與拆分。這樣一些系列事件下來,可能一個接單應用會流轉到多個部門,接單流程就會越來越豐富,可能包括多業務、多場景、個性化、各種開關、五花八門的擴展實現。
?
問題
在大接入背景下,我們聚焦在一個接單應用的一個接單方法上?;蚨嗷蛏僭诠ぷ髦卸紩龅揭幌聨追N問題:
?瀑布式迭代,一個方法最終三五千行,難以閱讀理解,牽一發動全身。
?大量個性化邏輯散落在下單得每個環節,梳理起來無從下手。
?方法串聯時上下文樣式各異,如果當初沒有擴展性,后期改動變動大。
?
思路
針對這些問題,可以分為兩個層面思考,戰略和戰術。
戰略
這里的戰略指的是模式,而接單場景可以利用工作臺模式,工人(組件)按順序圍著工作臺(上下文)生產兩件(執行任務),資源(參數)從工作臺拿取,這種模式可以做到組件解耦、穩定、可復用。保證業務流程靈活。
戰術
圍繞著工作臺模式,可以提煉的一下幾個關鍵點:
?組件定義
?上下文
?執行規則
?
組件定義
組件通常被定義為規則執行的最小單元,我們最常見的是普通組件,也就是調用就執行,這是大部分系統目前都在使用的組件。其實在流程規則中,組件就像編譯語言,還應該具備布爾組件、條件組件、循環組件、并行組件、異常捕獲組件等等。由于這些組件都可以包含在普通組件中,通過代碼來實現條件、循環等邏輯。所以在執行流程定義時,無法清晰識別這些本應體現在流程中的邏輯。
?
組件定義通常還有一個值得關注的就是組件的數量,哪些邏輯可以歸類到一個組件中,哪些需要分開。這里沒有標準答案,有兩個思路僅供參考:
1.如果訂單已經歸類不同子域,例如發貨、收貨、承運、產品、貨品等,那就按照對應子域劃分組件。這樣更容易達成語言統一。
2.根據流程中寫動作來定義組件,例如寫庫、下發wms、下配等。
上下文
在組件定義的時候都會定義上下文作為執行流程中出入參的載體。上下文得定義通常需要具備幾個特點:
?傳遞性
?共享性
?動態性
每個組件都只關心上下文中與自己相關的內容,可以進行讀取和更新,然后在流程中不斷傳遞下去。并且在需求迭代過程中支持擴展上下文。
?
執行規則
執行規則就是約定各種組件按照何種規則執行。這里實現方式大多xml方式、Spring注入方式、顯示組裝方式成執行鏈,然后順序執行。這種方案弊端就是無法體現條件判斷、循環、并行。另一個問題就是大家深受SpringBoot思維的“毒害”:約定大于配置,而逐漸放棄xml配置方式,讓執行鏈組裝藏在代碼中。讓執行規則更加不容易被發現,說白了就是執行規則沒有與代碼進行解耦。那么如果將執行規則單獨抽象出來,就可以更進一步支持多種方式存儲,例如數據庫、redis、ducc等,這樣熱更就會成為可能。
?
答案
在不斷實踐和學習中,我發現了一個具備上述所有能力的開源組件LiteFlow。
利用LiteFlow,你可以將瀑布流式的代碼,轉變成以組件為核心概念的代碼結構,這種結構的好處是可以任意編排,組件與組件之間是解耦的,組件可以用腳本來定義,組件之間的流轉全靠規則來驅動。LiteFlow擁有開源規則引擎最為簡單的DSL語法。十分鐘就可上手。
?
?
?
?
例子
要實現下面的流程:
?
?
流程規則:
?xml version="1.0" encoding="UTF-8"??>
THEN(
SWITCH(businessSwitch).TO(
// 中小件子流程
THEN(smallChain).id("small"),
// 冷鏈子流程
THEN(coldChain).id("cold")
),
// 迭代執行
ITERATOR(goodsIterator).DO(goodsItem),
// 選擇器+默認
SWITCH(kaSwitch).TO(dajiang, lining, nike).DEFAULT(defaultKa)
);
/chain?>
// 并行
WHEN(commonDept, smallWarehouse);
/chain?>
// 并行
WHEN(commonDept, coldWarehouse);
/chain?>
/flow?>
代碼結構:
.
├── LiteFlowDemoApplication.java
└── demos
└── web
├── BasicController.java
├── context
│ └── OrderContext.java
├── dto
│ ├── Dept.java
│ ├── Goods.java
│ ├── Request.java
│ └── WareHouse.java
├── enums
│ ├── BusinessEnum.java
│ └── KaEnum.java
└── node
├── BusinessSwitchCmp.java
├── ColdWarehouseCmp.java
├── CommonDeptCmp.java
├── GoodsItemCmp.java
├── GoodsIteratorCmp.java
├── KaSwitchCmp.java
├── SmallWarehouseCmp.java
└── ka
├── DaJiangCmp.java
├── DefaultCmp.java
├── LiNingCmp.java
└── NikeCmp.java
8 directories, 21 files
業務類型判斷:
@LiteflowComponent("businessSwitch")
public class BusinessSwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
Request request = this.getRequestData();
if(Objects.equals(request.getDept().getDeptNo(), "dept1")) {
return BusinessEnum.SMALL.getBusiness();
} else {
return BusinessEnum.COLD.getBusiness();
}
}
}
迭代器組件:
@LiteflowComponent("goodsIterator")
public class GoodsIteratorCmp extends NodeIteratorComponent {
@Override
public Iterator processIterator() throws Exception {
Request requestData = this.getRequestData();
return requestData.getGoodList().iterator();
}
}
循環執行:
@Slf4j
@LiteflowComponent("goodsItem")
public class GoodsItemCmp extends NodeComponent {
@Override
public void process() throws Exception {
log.info("goods item index = {}", this.getLoopIndex());
//獲取當前循環對象
Goods goods = this.getCurrLoopObj();
//賦值為當前循環索引
goods.setGoodsId(this.getLoopIndex());
OrderContext orderContext = this.getContextBean(OrderContext.class);
List goodsList = orderContext.getData("goods");
if(goodsList == null) {
goodsList = new ArrayList?>();
this.getContextBean(OrderContext.class).setData("goods", goodsList);
}
goodsList.add(goods);
}
}
測試用例
public String testConfig() {
Request request = new Request();
Dept dept = new Dept();
dept.setDeptNo("nike");
request.setDept(dept);
WareHouse wareHouse = new WareHouse();
request.setWareHouse(wareHouse);
Goods goods1 = new Goods();
goods1.setGoodsName("goods1");
Goods goods2 = new Goods();
goods2.setGoodsName("goods2");
request.setGoodList(Arrays.asList(goods1, goods2));
//參數1為流程標識,參數2為初始入參,參數3為上下文類型約定
LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain1",request, OrderContext.class);
//結果中獲取上下文
OrderContext contextBean = liteflowResponse.getContextBean(OrderContext.class);
List goodsList = contextBean.getData("goods");
WareHouse warehouse = contextBean.getData("warehouse");
Dept dept1 = contextBean.getData("dept");
log.info("=== dept = {}", JsonUtil.toJsonString(dept1));
log.info("=== warehouse = {}", JsonUtil.toJsonString(warehouse));
log.info("=== goodsList = {}", JsonUtil.toJsonString(goodsList));
return "yes";
}
特點
個人覺得LiteFlow的特點包括一下幾點:
?組件定義統一: 所有的邏輯都是組件,為所有的邏輯提供統一化的組件實現方式
?規則持久化: 框架原生支持把規則存儲在標準結構化數據庫,Nacos,Etcd,Zookeeper,Apollo,Redis、自定義擴展。
?上下文隔離機制: 可靠的上下文隔離機制,無需擔心高并發情況下的數據串流
?支持廣泛: Springboot,Spring還是任何其他java框架都支持。
?規則輕量: 基于規則文件來編排流程,學習規則門檻低
?
總結
LiteFlow是強大的流程規則框架,之所以沒有直接把LiteFlow放在標題中,是跟大家一起透過問題看本質,最終找到合適的解決方案,而LiteFlow通過設計和抽象能力解決問題,更加值得借鑒和學習。
?審核編輯 黃宇
-
組件
+關注
關注
1文章
505瀏覽量
17805 -
物流系統
+關注
關注
0文章
25瀏覽量
10688
發布評論請先 登錄
相關推薦
評論