Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

Time:2022-11-25

When we usually work on projects, we often encounter complex business logic. If we use if else to implement it, it will often be very lengthy and the maintenance cost will be high. Today I recommend a lightweight process engine to everyoneLiteFlow, which can elegantly implement complex business logic. This article will take the calculation of order prices in e-commerce projects as an example to talk about its use.

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

Introduction to LiteFlow

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 business logic into different components, and then use concise rule files to connect the entire process in series to realize complex business logic.

The main features of LiteFlow are as follows:

  • Unified definition of components: all logic is components, directly using Spring native annotations@ComponentJust define it.
  • Lightweight rules: The process is orchestrated based on rule files, and it only takes 5 minutes to learn the rules and expressions.
  • Diversified rules: The rules support three rule file writing methods: xml, json, and yml. Use whichever you like.
  • Arbitrary arrangement: Synchronous and asynchronous mixed arrangement, no matter how complicated the logical process is, can be easily realized.
  • Rules can be loaded from anywhere: the framework provides the implementation of local file configuration sources and zk configuration sources, and also provides extension interfaces.
  • Elegant Hot Refresh Mechanism: When the rules change, the application rules can be changed instantly without restarting the application.
  • Wide support: Colleagues support SpringBoot, Spring or other Java projects.

The following is the display page of using LiteFlow to realize order price calculation, which is indeed more elegant to implement!

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

IDEA plugin

LiteFlow also has its own IDEA pluginLiteFlowX, through this plug-in, it can support functions such as intelligent prompting of rule files, syntax highlighting, jumping between components and rule files, and LiteFlow toolbox. It is strongly recommended that you install it.

  • First, we install the plugin in IDEA’s plugin market;

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

  • After installing the LiteFlowX plug-in, the components and rule files defined in our code will display specific icons;

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

  • When we edit the rule file, we will be prompted for the defined components and support jumping from the rule file to the component;

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

  • It also supports opening the toolbox from the right to quickly view components and rule files.

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

regular expression

Next, let’s learn regular expressions, that is, the writing of rule files. The entry expressions are very simple, but this is very helpful for using LiteFlow!

serial arrangement

When we want to execute the four components a, b, c, and d in sequence, use it directlyTHENkeywords.

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

Parallel orchestration

If you want to execute the three components a, b, and c in parallel, you can useWHENkeywords.

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

select arrangement

If you want to implement the switch logic in the code, for example, judge by the return result of component a, if the component name b is returned, component b will be executed, you can useSWITCHkeywords.

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

conditional programming

If you want to implement the if logic in the code, for example, execute a when the x component returns true, you can useIFkeywords.

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

If you want to implement the ternary operator logic of if, for example, execute a component when the x component returns true, and execute 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 useELSEKeyword, equivalent to the effect achieved above.

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

If you want to implement else if logic, you can useELIFkeywords.

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

use subprocess

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

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

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

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

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

use

After learning regular expressions, we found that LiteFlow can implement complex processes with just a few keywords. Next, we will take the calculation of order price as an example to practice the process engine framework of LiteFlow.

  • First of all, we need to integrate LiteFlow in the project. Here we take the SpringBoot application as an example. Inpom.xmlJust add the following dependencies in:
<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>liteflow-spring-boot-starter</artifactId>
    <version>2.8.5</version>
</dependency>
  • Next modify the configuration fileapplication.yml, configure the LiteFlow rule file path;
server:
  port: 8580
liteflow:
  #rule file path
  rule-source: liteflow/*.el.xml
  • The official LiteFlow demo is directly used here. This case 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:

https://gitee.com/bryan31/lit…

  • After the download is complete, run the Demo directly, and access the test page through the following address: http://localhost:8580

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

  • In this case, the final price of the order can be calculated through the incoming order data, which involves operations such as member discounts, promotional offers, coupon deduction, and freight calculation. There are as many as a dozen steps, and it can be realized without using the process engine. It is very complicated. The following is the execution flow chart of each component in the order price calculation;

Say goodbye to if else! Try this lightweight process engine, the built-in IDEA plug-in is really delicious!

  • Next, let’s talk about how to use LiteFlow to achieve this function. First, we need to define each component. Common components need to inheritNodeComponentand achieveprocess()method, such as the coupon deduction component here, also needs to be set@ComponentThe name of the annotation, which can be overridden byisAccessmethod to decide whether to execute the component;
/**
 * Coupon deduction calculation component
 */
@Component("couponCmp")
public class CouponCmp extends NodeComponent {
    @Override
    public void process() throws Exception {
        PriceContext context = this.getContextBean(PriceContext.class);

        /**The face value of the coupon obtained under the Mock according to the couponId is 15 yuan**/
        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 special components, such as the condition component used to determine whether to calculate according to domestic freight calculation rules or overseas rules, which need to be inheritedNodeSwitchComponentand achieveprocessSwitch()method;
/**
 * Shipping condition component
 */
@Component("postageCondCmp")
public class PostageCondCmp extends NodeSwitchComponent {
    @Override
    public String processSwitch() throws Exception {
        PriceContext context = this.getContextBean(PriceContext.class);
        //According to the parameter overseas to determine whether to purchase overseas, go to the corresponding component
        boolean oversea = context.isOversea();
        if(oversea){
            return "overseaPostageCmp";
        }else{
            return "postageCmp";
        }
    }
}
  • For other component logic, please refer to the demo source code. After the components are defined, all the processes can be connected through the rule file. The first is the promotion discount calculation sub-process;
<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <chain name="promotionChain">
        THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp);
    </chain>
</flow>
  • Then there is the whole process. You can compare the flow chart above. Basically, those who can draw the flow chart can be realized 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 callFlowExecutorThe execution method of the class can be;
@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 next step. However, 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 the data in the process are stored uniformly, such as the abovePriceContextkind;
public class PriceContext {

    /**
     * order number
     */
    private String orderNo;

    /**
     * Whether overseas purchase
     */
    private boolean oversea;

    /**
     * Commodity package
     */
    private List<ProductPackVO> productPackList;

    /**
     * Order channel
     */
    private OrderChannelEnum orderChannel;

    /**
     * Member CODE
     */
    private String memberCode;

    /**
     * coupon
     */
    private Long couponId;

    /**
     * discount information
     */
    private List<PromotionPackVO> promotionPackList;

    /**
     * Price step
     */
    private List<PriceStepVO> priceStepList = new ArrayList<>();

    /**
     * Order original price
     */
    private BigDecimal originalOrderPrice;

    /**
     * Order final price
     */
    private BigDecimal finalOrderPrice;

    /**
     * Step log
     */
    private String printLog;
}
  • in the initialization context ofslotInitCmpcomponent, we have already changed fromgetRequestData()The requested order parameters are obtained in the method, and then set toPriceContextIn the context, other parameters and results in the process are also stored here.
/**
 * Slot initialization component
 */
@Component("slotInitCmp")
public class SlotInitCmp extends NodeComponent {
    @Override
    public void process() throws Exception {
        // Redundant the main parameters into the 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;
        }
    }
}

Summarize

LiteFlow is indeed an easy-to-use lightweight process engine that can make complex business logic clear and facilitate code maintenance. Compared with other process engines, its rule file is much easier to write, and you can get started in a few minutes. Interested friends can try it!

References

Official website document: https://liteflow.yomahub.com/

Project source address

https://gitee.com/dromara/lit…