Mastering LiteFlow: Build Elegant Java Workflow Engines for Complex Business Logic
This article introduces LiteFlow, a lightweight Java workflow engine, explains its core features, demonstrates rule syntax for serial, parallel, switch, and conditional orchestration, and provides step‑by‑step integration examples—including Maven dependencies, configuration, component implementation, and controller usage—to simplify complex business processes.
LiteFlow Overview
LiteFlow is a lightweight, powerful Chinese workflow engine framework designed for orchestrating complex component‑based business logic. It allows developers to define business logic in separate components and connect them using concise rule files.
Main features include:
Unified component definition using Spring native
@Componentannotation.
Lightweight rule files; beginners can learn the expression language in about five minutes.
Support for XML, JSON, and YAML rule file formats.
Flexible orchestration of synchronous and asynchronous processes.
Rule sources can be loaded from local files or Zookeeper, with extensible interfaces.
Hot‑refresh mechanism that applies rule changes without restarting the application.
Broad compatibility with Spring Boot, Spring, or other Java projects.
IDEA Plugin
LiteFlow provides an IDEA plugin
LiteFlowXthat offers intelligent rule file suggestions, syntax highlighting, navigation between components and rule files, and a toolbox for quick access.
Rule Expressions
Key keywords enable various orchestration patterns:
THEN – Serial execution (e.g.,
THEN(a, b, c, d);).
WHEN – Parallel execution (e.g.,
WHEN(a, b, c);).
SWITCH – Switch logic based on a component’s result (e.g.,
SWITCH(a).to(b, c, d);).
IF – Conditional execution (e.g.,
IF(x, a);).
ELSE – Equivalent to
IFwith else branch (e.g.,
IF(x, a).ELSE(b);).
ELIF – Else‑if chaining (e.g.,
IF(x1, a).ELIF(x2, b).ELSE(c);).
Sub‑processes can be defined and invoked to keep complex flows clear.
Integration Example
To integrate LiteFlow into a Spring Boot project, add the starter dependency:
<code><dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.8.5</version>
</dependency></code>Configure the rule file location in
application.yml:
<code>server:
port: 8580
liteflow:
# Rule file path
rule-source: liteflow/*.el.xml</code>Define components by extending
NodeComponentand implementing
process(). Example of a coupon discount component:
<code>@Component("couponCmp")
public class CouponCmp extends NodeComponent {
@Override
public void process() throws Exception {
PriceContext context = this.getContextBean(PriceContext.class);
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() {
return this.getContextBean(PriceContext.class).getCouponId() != null;
}
}</code>Conditional components can extend
NodeSwitchComponentand implement
processSwitch()to decide the next node based on context data.
<code>@Component("postageCondCmp")
public class PostageCondCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
PriceContext context = this.getContextBean(PriceContext.class);
return context.isOversea() ? "overseaPostageCmp" : "postageCmp";
}
}</code>Define rule files for sub‑processes and the main flow, for example:
<code><?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="promotionChain">
THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp);
</chain>
</flow></code> <code><?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></code>Expose an endpoint that invokes the flow via
FlowExecutor:
<code>@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";
}
}
}</code>The shared
PriceContextobject stores all intermediate data, such as order details, member information, coupon IDs, promotion packs, and price steps, enabling components to read and write without explicit parameter passing.
<code>public class PriceContext {
private String orderNo;
private boolean oversea;
private List<ProductPackVO> productPackList;
private OrderChannelEnum orderChannel;
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;
// getters and setters omitted for brevity
}</code>By using LiteFlow, complex business processes become clear, maintainable, and easily extensible, with rule files that are simple to write and hot‑reloadable without restarting the application.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.