When we usually do projects, we often encounter complex business logic. If we use if else to implement, it is often very lengthy and maintenance costs are high. Today, I recommend a lightweight process engine LiteFlow
, which can elegantly implement complex business logic. This article will take the order price calculation 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, which can be used for the orchestration of complex componentized business. Through it, we can define business logic into different components, and then use concise rule files to connect the entire process to realize complex business logic.
The main features of LiteFlow are as follows:
- Unified component definition: All logic is a component, which can be defined directly using Spring's native annotations
@Component
. - Lightweight rules: The process is arranged based on the rule file, and it only takes 5 minutes to learn the introduction of rule expressions.
- Diversified rules: The rules support xml, json, and yml rule files, whichever you prefer.
- Arbitrary arrangement: synchronous and 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: The rules of the application can be changed immediately without restarting the application.
- Wide support: colleagues support SpringBoot, Spring or other Java projects.
The following is a display page that uses LiteFlow to implement order price calculation, which is indeed more elegant to implement!
IDEA plugin
LiteFlow also has its own IDEA plug-in LiteFlowX
, through which it can support intelligent prompting of rule files, syntax highlighting, jumping between components and rule files, and LiteFlow toolbox, etc. It is strongly recommended that you install Down.
- First, we install the plug-in in IDEA's plug-in market;
- After installing the LiteFlowX plugin, the components and rule files defined in our code will display specific icons;
- When we edit the rule file, we will be prompted for the defined components, and support jumping from the rule file to the component;
- It also supports opening the toolbox from the right side to quickly view components and rule files.
regular expression
Next, let's learn the rule expression, that is, the writing of the rule file. The entry expression is very simple, but it is very helpful for using LiteFlow!
serial orchestration
When we want to execute the four components a, b, c, and d in sequence, we can directly use the THEN
keyword.
<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 use the WHEN
keyword.
<chain name="chain1">
WHEN(a, b, c);
</chain>
Choose an arrangement
If you want to implement the switch logic in the code, for example, judge by the return result of the a component, if the component name b is returned, execute the b component, you can use the SWITCH
keyword.
<chain name="chain1">
SWITCH(a).to(b, c, d);
</chain>
conditional orchestration
If you want to implement the if logic in the 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, when the x component returns true, execute the a component, and when it returns false, execute the b component, 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 above implementation.
<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 subprocess
When some processes are complicated, we can define sub-processes and then refer to 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 reference 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 only a few keywords. Next, we will take order price calculation 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. Add the following dependencies in
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 LiteFlow rule file path;
server:
port: 8580
liteflow:
#规则文件路径
rule-source: liteflow/*.el.xml
- The official Demo of LiteFlow 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/liteflow-example
- After the download is complete, run the Demo directly and access the test page through the following address: http://localhost:8580
- In this case, the final price of the order can be calculated through the incoming order data, which involves operations such as membership discounts, promotional offers, coupon deductions, and shipping calculations. There are as many as a dozen steps, which can be realized if the process engine is not used. It is very complicated. The following is the execution flow chart of each component in the order price calculation;
- Next, let's talk about how to use LiteFlow to implement this function. First, we need to define each component. Common components need to inherit
NodeComponent
and implement theprocess()
method, such as the coupon here. Button component, you also need to set the name of the annotation@Component
, you can decide whether to execute the component by rewriting theisAccess
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 special components, such as the conditional components used to determine whether to calculate according to the domestic freight calculation rules or the overseas rules, you need to inherit the
NodeSwitchComponent
and implement theprocessSwitch()
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";
}
}
}
- For the logic of other components, please refer to the demo source code. After defining the components, you can connect all the processes 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 above flow chart. Basically, you can use LiteFlow to draw a flow chart;
<?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 next step. However, after using LiteFlow, there is no parameter transmission in the component, so how are the parameters in each process handled? In fact, there is a concept of context in LiteFlow, all data in the process are stored here, such as the above
PriceContext
class;
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 of the initialization context, we have already obtained the requested order parameter from thegetRequestData()
method, and then set it to thePriceContext
context, other in the process Parameters and results are also stored here.
/**
* 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;
}
}
}
Summarize
LiteFlow is indeed an easy-to-use lightweight process engine, which can make complex business logic clear and facilitate code maintenance. Compared with other process engines, its rule file is much simpler to write, and you can get started in a few minutes. Interested friends can try it!
References
Official website documentation: https://liteflow.yomahub.com/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。