在我們平時做項目的時候,經常會遇到複雜的業務邏輯,如果使用if else來實作的話,往往會很冗長,維護成本也很高。今天給大家推薦一個輕量級流程引擎LiteFlow,可以優雅地實作複雜的業務邏輯,本文将以電商項目中的訂單價格計算為例來聊聊它的使用。
SpringBoot實戰電商項目mall(50k+star)位址:github.com/macrozheng/…
LiteFlow簡介
LiteFlow是一個輕量且強大的國産流程引擎架構,可用于複雜的元件化業務的編排工作。通過它我們可以把業務邏輯都定義到不同元件之中,然後使用簡潔的規則檔案來串聯整個流程,進而實作複雜的業務邏輯。
LiteFlow主要特性如下:
- 元件定義統一:所有的邏輯都是元件,直接使用Spring原生注解@Component定義即可。
- 規則輕量:基于規則檔案來編排流程,學習規則表達式入門僅需5分鐘。
- 規則多樣化:規則支援xml、json、yml三種規則檔案寫法,喜歡哪種用哪個。
- 任意編排:同步異步混編,再複雜的邏輯過程,都能輕易實作。
- 規則能從任意地方加載:架構中提供本地檔案配置源和zk配置源的實作,也提供了擴充接口。
- 優雅熱重新整理機制:規則變化,無需重新開機應用,即時改變應用的規則。
- 支援廣泛:同僚支援SpringBoot,Spring或其他Java項目。
下面是使用LiteFlow來實作訂單價格計算的展示頁面,實作起來确實比較優雅!
IDEA插件
LiteFlow還擁有自己的IDEA插件LiteFlowX,通過該插件能支援規則檔案的智能提示、文法高亮、元件與規則檔案之間的跳轉及LiteFlow工具箱等功能,強烈建議大家安裝下。
- 首先我們在IDEA的插件市場中安裝該插件;
- 安裝好LiteFlowX插件後,我們代碼中所定義的元件和規則檔案都會顯示特定的圖示;
- 當我們編輯規則檔案時,會提示我們已經定義好的元件,并支援從規則檔案中跳轉到元件;
- 還支援從右側打開工具箱,快捷檢視元件和規則檔案。
規則表達式
接下來我們學習下規則表達式,也就是規則檔案的編寫,入門表達式非常簡單,但這對使用LiteFlow非常有幫助!
串行編排
當我們想要依次執行a、b、c、d四個元件時,直接使用THEN關鍵字即可。
<chain name="chain1">
THEN(a, b, c, d);
</chain>
複制代碼
并行編排
如果想并行執行a、b、c三個元件的話,可以使用WHEN關鍵字。
<chain name="chain1">
WHEN(a, b, c);
</chain>
複制代碼
選擇編排
如果想實作代碼中的switch邏輯的話,例如通過a元件的傳回結果進行判斷,如果傳回的是元件名稱b的話則執行b元件,可以使用SWITCH關鍵字。
<chain name="chain1">
SWITCH(a).to(b, c, d);
</chain>
複制代碼
條件編排
如果想實作代碼中的if邏輯的話,例如當x元件傳回為true時執行a,可以使用IF關鍵字。
<chain name="chain1">
IF(x, a);
</chain>
複制代碼
如果想實作if的三元運算符邏輯的話,例如x元件傳回為true時執行a元件,傳回為false時執行b元件,可以編寫如下規則檔案。
<chain name="chain1">
IF(x, a, b);
</chain>
複制代碼
如果想實作if else邏輯的話,可以使用ELSE關鍵字,和上面實作效果等價。
<chain name="chain1">
IF(x, a).ELSE(b);
</chain>
複制代碼
如果想實作else if邏輯的話,可以使用ELIF關鍵字。
<chain name="chain1">
IF(x1, a).ELIF(x2, b).ELSE(c);
</chain>
複制代碼
使用子流程
當某些流程比較複雜時,我們可以定義子流程,然後在主流程中引用,這樣邏輯會比較清晰。
例如我們有如下子流程,執行C、D元件。
<chain name="subChain">
THEN(C, D);
</chain>
複制代碼
然後我們直接在主流程中引用子流程即可。
<chain name="mainChain">
THEN(
A, B,
subChain,
E
);
</chain>
複制代碼
使用
學習完規則表達式後,我們發現LiteFlow寥寥幾個關鍵字,就可以實作複雜的流程了。下面我們将以訂單價格計算為例,實踐下LiteFlow這個流程引擎架構。
- 首先我們需要在項目中內建LiteFlow,這裡以SpringBoot應用為例,在pom.xml中添加如下依賴即可;
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.8.5</version>
</dependency>
複制代碼
- 接下來修改配置檔案application.yml,配置好LiteFlow的規則檔案路徑即可;
server:
port: 8580
liteflow:
#規則檔案路徑
rule-source: liteflow/*.el.xml
複制代碼
- 這裡直接使用LiteFlow官方的Demo,該案例為一個價格計算引擎,模拟了電商中對訂單價格的計算,并提供了簡單的界面,下載下傳位址如下:
gitee.com/bryan31/lit…
- 下載下傳完成後,直接運作Demo,通過如下位址可以通路測試頁面:http://localhost:8580
- 這個案例通過傳入的訂單資料,能計算出訂單的最終價格,這裡涉及到會員折扣、促銷優惠、優惠券抵扣、運費計算等操作,多達十幾步,如果不使用流程引擎的話實作起來是非常複雜的,下面是訂單價格計算中各元件執行流程圖;
- 接下來我們來聊聊如何使用LiteFlow來實作這個功能,首先我們需要定義好各個元件,普通元件需要繼承NodeComponent并實作process()方法,比如這裡的優惠券抵扣元件,還需設定@Component注解的名稱,可以通過重寫isAccess方法來決定是否執行該元件;
/**
* 優惠券抵扣計算元件
*/
@Component("couponCmp")
public class CouponCmp extends NodeComponent {
@Override
public void process() throws Exception {
PriceContext context = this.getContextBean(PriceContext.class);
/**這裡Mock下根據couponId取到的優惠卷面值為15元**/
Long couponId = context.getCouponId();
BigDecimal couponPrice = new BigDecimal(15);
BigDecimal prePrice = context.getLastestPriceStep().getCurrPrice();
BigDecimal currPrice = prePrice.subtract(couponPrice);
context.addPriceStep(new PriceStepVO(PriceTypeEnum.COUPON_DISCOUNT,
couponId.toString(),
prePrice,
currPrice.subtract(prePrice),
currPrice,
PriceTypeEnum.COUPON_DISCOUNT.getName()));
}
@Override
public boolean isAccess() {
PriceContext context = this.getContextBean(PriceContext.class);
if(context.getCouponId() != null){
return true;
}else{
return false;
}
}
}
複制代碼
- 還有一些比較特殊的元件,比如用于判斷是按國内運費計算規則來計算還是境外規則的條件元件,需要繼承NodeSwitchComponent并實作processSwitch()方法;
/**
* 運費條件元件
*/
@Component("postageCondCmp")
public class PostageCondCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
PriceContext context = this.getContextBean(PriceContext.class);
//根據參數oversea來判斷是否境外購,轉到相應的元件
boolean oversea = context.isOversea();
if(oversea){
return "overseaPostageCmp";
}else{
return "postageCmp";
}
}
}
複制代碼
- 其他元件邏輯具體可以參考demo源碼,定義好元件之後就可以通過規則檔案将所有流程連接配接起來了,首先是促銷優惠計算子流程;
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="promotionChain">
THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp);
</chain>
</flow>
複制代碼
- 然後是整個流程,大家可以對比下上面的流程圖,基本能畫出流程圖的都可以用LiteFlow來實作;
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="mainChain">
THEN(
checkCmp, slotInitCmp, priceStepInitCmp,
promotionConvertCmp, memberDiscountCmp,
promotionChain, couponCmp,
SWITCH(postageCondCmp).to(postageCmp, overseaPostageCmp),
priceResultCmp, stepPrintCmp
);
</chain>
</flow>
複制代碼
- 最後在Controller中添加接口,擷取傳入的訂單資料,然後調用FlowExecutor類的執行方法即可;
@Controller
public class PriceExampleController {
@Resource
private FlowExecutor flowExecutor;
@RequestMapping(value = "/submit", method = RequestMethod.POST)
@ResponseBody
public String submit(@Nullable @RequestBody String reqData) {
try {
PriceCalcReqVO req = JSON.parseObject(reqData, PriceCalcReqVO.class);
LiteflowResponse response = flowExecutor.execute2Resp("mainChain", req, PriceContext.class);
return response.getContextBean(PriceContext.class).getPrintLog();
} catch (Throwable t) {
t.printStackTrace();
return "error";
}
}
}
複制代碼
- 我們平時在寫複雜代碼時,後面一步經常會用到前面一步的結果,然而使用LiteFlow之後,元件裡并沒有參數傳遞,那麼各個流程中參數是這麼處理的?其實是LiteFlow中有個上下文的概念,流程中的所有資料都統一存放在此,比如上面的PriceContext類;
public class PriceContext {
/**
* 訂單号
*/
private String orderNo;
/**
* 是否境外購
*/
private boolean oversea;
/**
* 商品包
*/
private List<ProductPackVO> productPackList;
/**
* 訂單管道
*/
private OrderChannelEnum orderChannel;
/**
* 會員CODE
*/
private String memberCode;
/**
* 優惠券
*/
private Long couponId;
/**
* 優惠資訊
*/
private List<PromotionPackVO> promotionPackList;
/**
* 價格步驟
*/
private List<PriceStepVO> priceStepList = new ArrayList<>();
/**
* 訂單原始價格
*/
private BigDecimal originalOrderPrice;
/**
* 訂單最終價格
*/
private BigDecimal finalOrderPrice;
/**
* 步驟日志
*/
private String printLog;
}
複制代碼
- 在初始化上下文的slotInitCmp元件中,我們早已從getRequestData()方法中擷取到了請求的訂單參數,然後設定到了PriceContext上下文中,流程中的其他參數和結果也存儲在此了。
/**
* Slot初始化元件
*/
@Component("slotInitCmp")
public class SlotInitCmp extends NodeComponent {
@Override
public void process() throws Exception {
//把主要參數備援到slot裡
PriceCalcReqVO req = this.getRequestData();
PriceContext context = this.getContextBean(PriceContext.class);
context.setOrderNo(req.getOrderNo());
context.setOversea(req.isOversea());
context.setMemberCode(req.getMemberCode());
context.setOrderChannel(req.getOrderChannel());
context.setProductPackList(req.getProductPackList());
context.setCouponId(req.getCouponId());
}
@Override
public boolean isAccess() {
PriceCalcReqVO req = this.getSlot().getRequestData();
if(req != null){
return true;
}else{
return false;
}
}
}
複制代碼
總結
LiteFlow确實是一款好用的輕量級流程引擎,可以讓複雜的業務邏輯變得清晰起來,便于代碼維護。它的規則檔案比起其他流程引擎來說,編寫簡單太多了,幾分鐘就能上手,感興趣的朋友可以嘗試下它!