在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护的成本得就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现。
liteFlow是一个轻量,快速的组件式流程引擎框架/规则引擎,组件编排,组件复用,帮助解耦业务代码,让每一个业务片段都是一个优雅的组件,并支持热加载规则配置,实现即时修改。
项目主页请点击:项目主页
项目文档请点击:项目文档
示例工程请点击:示例工程
复杂业务的解耦编排利器,为所有组件提供统一化的实现方式
基于规则文件来编排流程,支持xml,json,yml三种规则文件写法方式
框架中提供本地文件配置源,zk配置源的实现
框架提供自定义配置源,只需实现一个接口,即可从任何地方加载配置源
支持SpringBoot的自动装配,也支持Spring的配置和非Spring的项目
提供串行和并行2种模式,提供常见常见的表达式语句
可以定义脚本语言节点,支持QLExpress和Groovy两种脚本
组件可以支持重试,每个组件均可自定义重试配置和指定异常
提供无级嵌套的显式子流程模式,隐式子流程模式
数据槽隔离机制,在多并发下上下文独立而稳定
支持优雅平滑热刷新特性
对系统损耗极低,可以稳定运行在核心业务大规模的微服务中
自带简单的监控,能够知道每个组件的运行耗时排行
LiteFlow就是为解耦复杂逻辑而生,如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个轻量,快速的组件式流程引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件,并支持热加载规则配置,实现即时修改。
使用LiteFlow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。这样,所有的组件,就能按照你的规则配置去进行复杂的流转。
LiteFlow是基于工作台模式进行设计的,何谓工作台模式?
n个工人按照一定顺序围着一张工作台,按顺序各自生产零件,生产的零件最终能组装成一个机器,每个工人只需要完成自己手中零件的生产,而无需知道其他工人生产的内容。每一个工人生产所需要的资源都从工作台上拿取,如果工作台上有生产所必须的资源,则就进行生产,若是没有,就等到有这个资源。每个工人所做好的零件,也都放在工作台上。
这个模式有几个好处:
每个工人无需和其他工人进行沟通。工人只需要关心自己的工作内容和工作台上的资源。这样就做到了每个工人之间的解耦和无差异性。
即便是工人之间调换位置,工人的工作内容和关心的资源没有任何变化。这样就保证了每个工人的稳定性。
如果是指派某个工人去其他的工作台,工人的工作内容和需要的资源依旧没有任何变化,这样就做到了工人的可复用性。
因为每个工人不需要和其他工人沟通,所以可以在生产任务进行时进行实时工位更改:替换,插入,撤掉一些工人,这样生产任务也能实时的被更改。这样就保证了整个生产任务的灵活性。
这个模式映射到LiteFlow框架里,工人就是组件,工人坐的顺序就是流程配置,工作台就是上下文,资源就是参数,最终组装的这个机器就是这个业务。正因为有这些特性,所以LiteFlow能做到统一解耦的组件和灵活的装配。
LiteFlow自开源来,经常有一些小伙伴来问我,如何做角色任务之间的流转,类似于审批流,A审批完应该是B审批,然后再流转到C角色。
这里申明下,LiteFlow只做基于逻辑的流转,而不做基于角色任务的流转。如果你想做基于角色任务的流转,推荐使用flowable,activiti这2个框架。
LiteFlow适用于拥有复杂逻辑的业务,比如说价格引擎,下单流程等,这些业务往往都拥有很多步骤,这些步骤完全可以按照业务粒度拆分成一个个独立的组件,进行装配复用变更。使用LiteFlow,你会得到一个灵活度高,扩展性很强的系统。因为组件之间相互独立,也也可以避免改一处而动全身的这样的风险。
Flowable和Activiti都是极为优秀的流程引擎框架,其中Flowable的底层也是Activiti,他们除了能做基于任务角色的流程,也能做基于逻辑的流程,并且他们的基于BPM协议,很多基于BPM协议的编辑工具都能为他们可视化编辑流程。
LiteFlow和他们相比,虽然功能不如他们那么多,但是胜在轻量,高性能和极少学习成本上。而且这2款都是国外开源,集成起来比较重,而且文档本地化做的也不够好。LiteFlow拥有完善的本地化文档和使用范例。在大部分的场景可以帮助你改善你的系统。
案例基于liteflow-example实现
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zrj</groupId>
<artifactId>liteflow-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>liteflow-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<commons.lang3.version>3.4</commons.lang3.version>
<commons-collections.version>4.1</commons-collections.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
server.port=8580
liteflow.ruleSource=liteflow/*.xml
#-----------------以下非必须-----------------
##liteflow是否开启,默认为true
#liteflow.enable=true
##liteflow的banner是否开启,默认为true
#liteflow.print-banner=true
##zkNode的节点,只有使用zk作为配置源的时候才起作用
#liteflow.zk-node=/lite-flow/flow
##slot的数量,默认值为1024
#liteflow.slot-size=1024
##异步线程最长的等待时间秒(只用于when),默认值为15
#liteflow.when-max-wait-second=15
##异步线程池最大线程数,默认为16
#liteflow.when-max-workers=16
##异步线程池等待队列数,默认为512
#liteflow.when-queue-limit=512
##是否在启动的时候就解析规则,默认为true
#liteflow.parse-on-start=true
##全局重试次数,默认为0
#liteflow.retry-count=0
##是否支持不同类型的加载方式混用,默认为false
#liteflow.support-multiple-type=false
##是否开启监控log打印,默认值为false
#liteflow.monitor.enable-log=true
##监控队列存储大小,默认值为200
#liteflow.monitor.queue-limit=300
##监控一开始延迟多少执行,默认值为300000毫秒,也就是5分钟
#liteflow.monitor.delay=10000
##监控日志打印每过多少时间执行一次,默认值为300000毫秒,也就是5分钟
#liteflow.monitor.period=10000
activityFlow.xml
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="activityFlow">
<then value="activitySlotInitCmp"/>
<then value="activityParamCheckCmp"/>
<then value="activityToolsCheckCmp"/>
</chain>
</flow>
ActivityReqVO
package com.zrj.liteflow.bean.activity;
import com.yomahub.liteflow.entity.data.AbsSlot;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 活动请求对象
*
* @author zrj
* @since 2021/11/15
**/
@Data
@Builder
public class ActivityReqVO extends AbsSlot {
/**
* 活动编码
*/
private String activityCode;
/**
* 活动名称
*/
private String activityName;
/**
* 活动开始时间
*/
private Date validStartTime;
/**
* 活动结束时间
*/
private Date validEndTime;
/**
* 活动工具
*/
private List<ActivityTools> activityToolsList;
}
ActivityTools
package com.zrj.liteflow.bean.activity;
import lombok.Builder;
import lombok.Data;
/**
* @author zrj
* @since 2021/11/15
**/
@Data
@Builder
public class ActivityTools {
/**
* 活动编码
*/
private String activityCode;
/**
* 活动工具编码
*/
private String actToolCode;
/**
* 活动工具名称
*/
private String actToolName;
}
ActivityParamCheckCmp
package com.zrj.liteflow.component.activity;
import com.yomahub.liteflow.core.NodeComponent;
import com.zrj.liteflow.slot.ActivitySlot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 活动请求参数校验
*
* @author zrj
* @since 2021/11/15
**/
@Slf4j
@Component("activityParamCheckCmp")
public class ActivityParamCheckCmp extends NodeComponent {
@Override
public void process() throws Exception {
log.info("【活动请求参数校验】");
ActivitySlot activitySlot = this.getSlot();
if (!checkActParam(activitySlot.getActivityCode())) {
activitySlot.setPrintLog("活动编码为空异常");
throw new RuntimeException("活动编码为空异常");
}
}
/**
* 参数校验
*/
private boolean checkActParam(String code) {
if (code == null) {
return false;
}
return true;
}
}
ActivitySlotInitCmp
package com.zrj.liteflow.component.activity;
import com.yomahub.liteflow.core.NodeComponent;
import com.zrj.liteflow.bean.activity.ActivityReqVO;
import com.zrj.liteflow.slot.ActivitySlot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 初始化活动数据槽
*
* @author zrj
* @since 2021/11/15
**/
@Slf4j
@Component("activitySlotInitCmp")
public class ActivitySlotInitCmp extends NodeComponent {
@Override
public void process() throws Exception {
log.info("【初始化活动数据槽】");
ActivityReqVO req = this.getSlot().getRequestData();
ActivitySlot activitySlot = this.getSlot();
activitySlot.setActivityCode(req.getActivityCode());
activitySlot.setActivityName(req.getActivityName());
activitySlot.setActivityToolsList(req.getActivityToolsList());
}
/**
* 是否进入该节点
*
* @return boolean
*/
@Override
public boolean isAccess() {
ActivityReqVO req = this.getSlot().getRequestData();
if (req != null) {
return true;
} else {
return false;
}
}
}
ActivityToolsCheckCmp
package com.zrj.liteflow.component.activity;
import com.yomahub.liteflow.core.NodeComponent;
import com.zrj.liteflow.bean.activity.ActivityTools;
import com.zrj.liteflow.slot.ActivitySlot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 活动请求参数校验
*
* @author zrj
* @since 2021/11/15
**/
@Slf4j
@Component("activityToolsCheckCmp")
public class ActivityToolsCheckCmp extends NodeComponent {
@Override
public void process() throws Exception {
log.info("【活动工具校验】");
ActivitySlot activitySlot = this.getSlot();
List<ActivityTools> activityToolsList = activitySlot.getActivityToolsList();
activitySlot.setPrintLog("活动流程测试成功");
}
}
ActivitySlot
package com.zrj.liteflow.slot;
import com.yomahub.liteflow.entity.data.AbsSlot;
import com.zrj.liteflow.bean.activity.ActivityTools;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 活动数据槽
*
* @author zrj
* @since 2021/11/15
**/
@Data
public class ActivitySlot extends AbsSlot {
/**
* 活动编码
*/
private String activityCode;
/**
* 活动名称
*/
private String activityName;
/**
* 活动开始时间
*/
private Date validStartTime;
/**
* 活动结束时间
*/
private Date validEndTime;
/**
* 活动工具
*/
private List<ActivityTools> activityToolsList;
/**
* 步骤日志
*/
private String printLog;
}
ActivityController
package com.zrj.liteflow.controller;
import cn.hutool.core.date.DateUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.entity.data.LiteflowResponse;
import com.zrj.liteflow.bean.activity.ActivityReqVO;
import com.zrj.liteflow.bean.activity.ActivityTools;
import com.zrj.liteflow.slot.ActivitySlot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author zrj
* @since 2021/11/15
**/
@RestController
@RequestMapping("/activity")
public class ActivityController {
@Resource
private FlowExecutor flowExecutor;
@GetMapping("/liteflow")
public String liteflow() {
ActivityReqVO activityReqVO = builder();
//ActivityReqVO activityReqVO = null;
try {
LiteflowResponse<ActivitySlot> response = flowExecutor.execute2Resp("activityFlow", activityReqVO, ActivitySlot.class);
return response.getSlot().getPrintLog();
} catch (Throwable t) {
t.printStackTrace();
return "error";
}
}
/**
* 构建请求对象
*/
private ActivityReqVO builder() {
List<ActivityTools> activityToolsList = new ArrayList<>(10);
activityToolsList.add(ActivityTools.builder().activityCode("20211115001").actToolName("大转盘").build());
activityToolsList.add(ActivityTools.builder().activityCode("20211115001").actToolName("红包雨").build());
return ActivityReqVO.builder()
.activityCode("20211115001")
.activityName("双十一大促")
.validStartTime(DateUtil.date())
.validEndTime(DateUtil.date())
.activityToolsList(activityToolsList)
.build();
}
}
输出验证
http://localhost:8580/activity/liteflow
2021-11-15 19:05:26.017 INFO 6844 --- [nio-8580-exec-8] c.z.l.c.activity.ActivitySlotInitCmp : 【初始化活动数据槽】
2021-11-15 19:05:26.017 INFO 6844 --- [nio-8580-exec-8] com.yomahub.liteflow.core.NodeComponent : [33776573532900]:[O]start component[ActivityParamCheckCmp] execution
2021-11-15 19:05:26.017 INFO 6844 --- [nio-8580-exec-8] c.z.l.c.activity.ActivityParamCheckCmp : 【活动请求参数校验】
2021-11-15 19:05:26.017 INFO 6844 --- [nio-8580-exec-8] com.yomahub.liteflow.core.NodeComponent : [33776573532900]:[O]start component[ActivityToolsCheckCmp] execution
2021-11-15 19:05:26.017 INFO 6844 --- [nio-8580-exec-8] c.z.l.c.activity.ActivityToolsCheckCmp : 【活动工具校验】
2021-11-15 19:05:26.017 INFO 6844 --- [nio-8580-exec-8] com.yomahub.liteflow.entity.data.Slot : [33776573532900]:CHAIN_NAME[activityFlow]
activitySlotInitCmp==>activityParamCheckCmp==>activityToolsCheckCmp