laitimes

Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!

author:Xiao Man just wanted to sleep
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!
When we usually do projects, we often encounter complex business logic, and if we use if else to implement, it is often verbose and expensive to maintain. Today I recommend a lightweight process engine LiteFlow to elegantly implement complex business logic, and this article will take the order price calculation in e-commerce projects as an example to talk about its use.

SpringBoot practical e-commerce project mall (50k+star) address: github.com/macrozheng/...

LiteFlow profile

LiteFlow is a lightweight and powerful domestic process engine framework that can be used for the orchestration of complex componentized businesses. Through it, we can define the business logic into different components, and then use a concise rule file to connect the entire process to achieve complex business logic.

The main features of LiteFlow are as follows:

  • Unified component definition: All logic is a component, which can be defined directly @Component using Spring native annotations.
  • Lightweight rules: Orchestrate processes based on rule files, and it only takes 5 minutes to learn rule expressions.
  • Rule diversification: Rules support XML, JSON, and YML rule file writing, which one you like to use.
  • Arbitrary arrangement: synchronous asynchronous mixing, no matter how complex the logic process, can be easily realized.
  • Rules can be loaded from anywhere: the framework provides implementations of local file configuration sources and zk configuration sources, as well as extended interfaces.
  • Elegant hot refresh mechanism: rule changes, no need to restart the application, instant change of application rules.
  • Broad support: Colleagues support SpringBoot, Spring, or other Java projects.

Below is a display page for using LiteFlow to calculate the price of an order, which is indeed quite elegant to implement!

Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!

IDEA plugin

LiteFlow also has its own IDEA plugin, LiteFlowX, through which it can support rules file IntelliHint, syntax highlighting, jumps between components and rule files, and LiteFlow toolbox, which is highly recommended for installation.
  • First we install the plugin in IDEA's plugin market;
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!
  • After installing the LiteFlowX plugin, the components and rule files defined in our code will display specific icons;
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!
  • When we edit the rule file, we will be prompted with the components we have defined, and we support jumping to the components from the rule file;
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!
  • It also supports opening the toolbox from the right side to quickly view component and rule files.
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!

Rule expressions

Next, let's learn about rule expressions, that is, writing rule files, which are very simple to get started, but very helpful for using LiteFlow!

Serial orchestration

When we want to execute the four components of a, b, c, and d in turn, we can directly use the THEN keyword.

<chain name="chain1">
    THEN(a, b, c, d);
</chain>
复制代码           

Parallel orchestration

If you want to execute a, b, and c components in parallel, you can use the WHEN keyword.

<chain name="chain1">
    WHEN(a, b, c);
</chain>
复制代码           

Select Orchestration

If you want to implement the switch logic in the code, for example, judging by the return result of the a component, and execute the b component if the component name b is returned, you can use the SWITCH keyword.

<chain name="chain1">
    SWITCH(a).to(b, c, d);
</chain>
复制代码           

Conditional orchestration

If you want to implement if logic in your code, such as executing a when the x component returns true, you can use the IF keyword.

<chain name="chain1">
    IF(x, a);
</chain>
复制代码           

If you want to implement the ternary operator logic of if, for example, execute the A component when the X component returns true, and execute the B component when it returns false, you can write the following rule file.

<chain name="chain1">
    IF(x, a, b);
</chain>
复制代码           

If you want to implement if else logic, you can use the ELSE keyword, which is equivalent to the effect above.

<chain name="chain1">
    IF(x, a).ELSE(b);
</chain>
复制代码           

If you want to implement else if logic, you can use the ELIF keyword.

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

Use sub-processes

When some processes are more complex, we can define sub-processes and then reference them in the main process so that the logic is clearer.

For example, we have the following sub-process to execute C and D components.

<chain name="subChain">
  	THEN(C, D);
</chain>
复制代码           

Then we can refer to the sub-process directly in the main process.

<chain name="mainChain">
    THEN(
    	A, B,
    	subChain,
    	E
    );
</chain>
复制代码           

use

After learning the regular expressions, we found that LiteFlow can implement complex processes with only a few keywords. Below we will take the order price calculation as an example to put into practice the process engine framework of LiteFlow.
  • First of all, we need to integrate LiteFlow in the project, here take the SpringBoot application as an example, add the following dependencies to the pom .xml;
<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>liteflow-spring-boot-starter</artifactId>
    <version>2.8.5</version>
</dependency>
复制代码           
  • Next, modify the configuration file application.yml and configure the rule file path of LiteFlow;
server:
  port: 8580
liteflow:
  #规则文件路径
  rule-source: liteflow/*.el.xml
复制代码           
  • LiteFlow official demo is directly used here, which is a price calculation engine that simulates the calculation of order prices in e-commerce, and provides a simple interface, the download address is as follows:
gitee.com/bryan31/lit…
  • After the download is complete, run the demo directly, and you can access the test page through the following address: http://localhost:8580
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!
  • This case through the incoming order data, can calculate the final price of the order, here involves membership discounts, promotional offers, coupon deductions, freight calculations and other operations, up to a dozen steps, if not using the process engine to achieve is very complex, the following is the order price calculation in the component execution flow chart;
Say goodbye if else! Try this lightweight process engine, which comes with the IDEA plugin!
  • Next, let's talk about how to use LiteFlow to achieve this function, first we need to define each component, ordinary components need to inherit NodeComponent and implement the process() method, such as the coupon deduction component here, but also need to set the name of the @Component annotation, you can decide whether to execute the component by overriding the isAccess method;
/**
 * 优惠券抵扣计算组件
 */
@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;
        }
    }
}
复制代码           
  • There are also some more special components, such as the conditional components used to determine whether to calculate according to the domestic freight calculation rules or overseas rules, which need to inherit the NodeSwitchComponent and implement the processSwitch() method;
/**
 * 运费条件组件
 */
@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";
        }
    }
}
复制代码           
  • Other component logic can refer to the demo source code, after defining the components, you can connect all processes through the rule file, first of all, the promotion offer calculation sub-process;
<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <chain name="promotionChain">
        THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp);
    </chain>
</flow>
复制代码           
  • Then the whole process, you can compare the above flowchart, basically can draw the flowchart can be implemented with 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>
复制代码           
  • Finally, add an interface in the Controller, get the incoming order data, and then call the execution method of the FlowExecutor class;
@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";
        }
    }
}
复制代码           
  • When we usually write complex code, the result of the previous step is often used in the latter step, but after using LiteFlow, there is no parameter passing in the component, so how are the parameters handled in each process? In fact, there is a concept of context in LiteFlow, where all data in the process is stored unified, such as the PriceContext class above;
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;
}
复制代码           
  • In the slotInitCmp component that initializes the context, we have already obtained the requested order parameters from the getRequestData() method, and then set them to the PriceContext context, where other parameters and results in the process are also stored.
/**
 * 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;
        }
    }
}
复制代码           

summary

LiteFlow is indeed a lightweight process engine that makes complex business logic clear and easy to maintain. Its rules file is much easier to write than other process engines, and you can get started in a few minutes, so interested friends can try it!