告別if else!試試這款輕量級流程引擎吧,自帶IDEA外掛真香!

語言: CN / TW / HK

在我們平時做專案的時候,經常會遇到複雜的業務邏輯,如果使用if else來實現的話,往往會很冗長,維護成本也很高。今天給大家推薦一個輕量級流程引擎LiteFlow,可以優雅地實現複雜的業務邏輯,本文將以電商專案中的訂單價格計算為例來聊聊它的使用。

SpringBoot實戰電商專案mall(50k+star)地址:http://github.com/macrozheng/mall

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關鍵字即可。

xml <chain name="chain1"> THEN(a, b, c, d); </chain>

並行編排

如果想並行執行a、b、c三個元件的話,可以使用WHEN關鍵字。

xml <chain name="chain1"> WHEN(a, b, c); </chain>

選擇編排

如果想實現程式碼中的switch邏輯的話,例如通過a元件的返回結果進行判斷,如果返回的是元件名稱b的話則執行b元件,可以使用SWITCH關鍵字。

xml <chain name="chain1"> SWITCH(a).to(b, c, d); </chain>

條件編排

如果想實現程式碼中的if邏輯的話,例如當x元件返回為true時執行a,可以使用IF關鍵字。

xml <chain name="chain1"> IF(x, a); </chain>

如果想實現if的三元運算子邏輯的話,例如x元件返回為true時執行a元件,返回為false時執行b元件,可以編寫如下規則檔案。

xml <chain name="chain1"> IF(x, a, b); </chain>

如果想實現if else邏輯的話,可以使用ELSE關鍵字,和上面實現效果等價。

xml <chain name="chain1"> IF(x, a).ELSE(b); </chain>

如果想實現else if邏輯的話,可以使用ELIF關鍵字。

xml <chain name="chain1"> IF(x1, a).ELIF(x2, b).ELSE(c); </chain>

使用子流程

當某些流程比較複雜時,我們可以定義子流程,然後在主流程中引用,這樣邏輯會比較清晰。

例如我們有如下子流程,執行C、D元件。

xml <chain name="subChain"> THEN(C, D); </chain>

然後我們直接在主流程中引用子流程即可。

xml <chain name="mainChain"> THEN( A, B, subChain, E ); </chain>

使用

學習完規則表示式後,我們發現LiteFlow寥寥幾個關鍵字,就可以實現複雜的流程了。下面我們將以訂單價格計算為例,實踐下LiteFlow這個流程引擎框架。

  • 首先我們需要在專案中整合LiteFlow,這裡以SpringBoot應用為例,在pom.xml中新增如下依賴即可;

xml <dependency> <groupId>com.yomahub</groupId> <artifactId>liteflow-spring-boot-starter</artifactId> <version>2.8.5</version> </dependency>

  • 接下來修改配置檔案application.yml,配置好LiteFlow的規則檔案路徑即可;

yaml server: port: 8580 liteflow: #規則檔案路徑 rule-source: liteflow/*.el.xml

  • 這裡直接使用LiteFlow官方的Demo,該案例為一個價格計算引擎,模擬了電商中對訂單價格的計算,並提供了簡單的介面,下載地址如下:

http://gitee.com/bryan31/liteflow-example

  • 下載完成後,直接執行Demo,通過如下地址可以訪問測試頁面:http://localhost:8580

  • 這個案例通過傳入的訂單資料,能計算出訂單的最終價格,這裡涉及到會員折扣、促銷優惠、優惠券抵扣、運費計算等操作,多達十幾步,如果不使用流程引擎的話實現起來是非常複雜的,下面是訂單價格計算中各元件執行流程圖;

  • 接下來我們來聊聊如何使用LiteFlow來實現這個功能,首先我們需要定義好各個元件,普通元件需要繼承NodeComponent並實現process()方法,比如這裡的優惠券抵扣元件,還需設定@Component註解的名稱,可以通過重寫isAccess方法來決定是否執行該元件;

```java /* * 優惠券抵扣計算元件 / @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()方法;

java /** * 運費條件元件 */ @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

THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp); ```

  • 然後是整個流程,大家可以對比下上面的流程圖,基本能畫出流程圖的都可以用LiteFlow來實現;

```xml

THEN( checkCmp, slotInitCmp, priceStepInitCmp, promotionConvertCmp, memberDiscountCmp, promotionChain, couponCmp, SWITCH(postageCondCmp).to(postageCmp, overseaPostageCmp), priceResultCmp, stepPrintCmp ); ```

  • 最後在Controller中新增介面,獲取傳入的訂單資料,然後呼叫FlowExecutor類的執行方法即可;

```java @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類;

```java 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上下文中,流程中的其他引數和結果也儲存在此了。

```java /* * 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確實是一款好用的輕量級流程引擎,可以讓複雜的業務邏輯變得清晰起來,便於程式碼維護。它的規則檔案比起其他流程引擎來說,編寫簡單太多了,幾分鐘就能上手,感興趣的朋友可以嘗試下它!

參考資料

官網文件:http://liteflow.yomahub.com/

專案原始碼地址

http://gitee.com/dromara/liteFlow

「其他文章」