文章目录
今天给大家介绍一款轻量、快速、稳定可编排的组件式规则引擎框架LiteFlow。
前言
在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护的成本得就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现。
LiteFlow为解耦逻辑而生,为编排而生,在使用LiteFlow之后,你会发现打造一个低耦合,灵活的系统会变得易如反掌!
1、LiteFlow框架的优势
如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个轻量,快速的组件式规则引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件,并支持热加载规则配置,实现即时修改。
使用LiteFlow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。这样,所有的组件,都能按照你的规则配置去进行复杂的流转。
2、LiteFlow的设计原则
LiteFlow是基于工作台模式进行设计的,何谓工作台模式?
n个工人按照一定顺序围着一张工作台,按顺序各自生产零件,生产的零件最终能组装成一个机器,每个工人只需要完成自己手中零件的生产,而无需知道其他工人生产的内容。每一个工人生产所需要的资源都从工作台上拿取,如果工作台上有生产所必须的资源,则就进行生产,若是没有,就等到有这个资源。每个工人所做好的零件,也都放在工作台上。
这个模式有几个好处:
- 每个工人无需和其他工人进行沟通。工人只需要关心自己的工作内容和工作台上的资源。这样就做到了每个工人之间的解耦和无差异性。
- 即便是工人之间调换位置,工人的工作内容和关心的资源没有任何变化。这样就保证了每个工人的稳定性。
- 如果是指派某个工人去其他的工作台,工人的工作内容和需要的资源依旧没有任何变化,这样就做到了工人的可复用性。
- 因为每个工人不需要和其他工人沟通,所以可以在生产任务进行时进行实时工位更改:替换,插入,撤掉一些工人,这样生产任务也能实时的被更改。这样就保证了整个生产任务的灵活性。
这个模式映射到LiteFlow框架里,工人就是组件,工人坐的顺序就是流程配置,工作台就是上下文,资源就是参数,最终组装的这个机器就是这个业务。正因为有这些特性,所以LiteFlow能做到统一解耦的组件和灵活的装配。
3、LiteFlow不适用于哪些场景
LiteFlow只做基于逻辑的流转,而不做基于角色任务的流转。如果你想做基于角色任务的流转,推荐使用flowable (opens new window),activiti (opens new window)这2个框架。
4、LiteFlow适用于哪些场景
LiteFlow适用于拥有复杂逻辑的业务,比如说价格引擎,下单流程等,这些业务往往都拥有很多步骤,这些步骤完全可以按照业务粒度拆分成一个个独立的组件,进行装配复用变更。使用LiteFlow,你会得到一个灵活度高,扩展性很强的系统。因为组件之间相互独立,也可以避免改一处而动全身的这样的风险。
5、LiteFlow特性
- 组件定义统一: 所有的逻辑都是组件,为所有的逻辑提供统一化的组件实现方式,小身材,大能量。
- 规则轻量: 基于规则文件来编排流程,学习规则表达式入门只需要5分钟,一看既懂。
- 规则多样化: 规则支持xml、json、yml三种规则文件写法方式,喜欢哪种用哪个。
- 任意编排: 同步异步混编,再复杂的逻辑过程,利用LiteFlow的规则,都是易如反掌,看规则文件就能知道逻辑是如何运转的,所见即所得。
- 规则能从任意地方加载: 框架中提供本地文件配置源和zk配置源的实现,也提供了扩展接口,您可以把规则存储在任何地方。
- 优雅热刷新机制: 规则变化,无需重启您的应用,即时改变应用的规则。高并发下不会因为刷新规则导致正在执行的规则有任何错乱。
- 支持广泛: 不管你的项目是不是基于Springboot,Spring还是任何其他java框架构建,LiteFlow都能游刃有余。
- JDK支持: 从JDK8到JDK17,统统支持。无需担心JDK版本。
- 脚本语言支持: 可以定义脚本语言节点,支持QLExpress和Groovy两种脚本。未来还会支持更多的脚本语言。
- 规则嵌套支持: 只要你想的出,你可以利用简单的表达式完成多重嵌套的复杂逻辑编排。
- 组件重试支持: 组件可以支持重试,每个组件均可自定义重试配置和指定异常。
- 上下文隔离机制: 可靠的上下文隔离机制,你无需担心高并发情况下的数据串流。
- 声明式组件支持: 你可以让你的任意类秒变组件。
- 详细的步骤信息: 你的链路如何执行的,每个组件耗时多少,报了什么错,一目了然。
- 稳定可靠: 历时2年多的迭代,在各大公司的核心系统上稳定运行。
- 性能卓越: 框架本身几乎不消耗额外性能,性能取决你的组件执行效率。
- 自带简单监控: 框架内自带一个命令行的监控,能够知道每个组件的运行耗时排行。
6、SpringBoot场景应用
6.1 依赖
LiteFlow提供了liteflow-spring-boot-starter依赖包,提供自动装配功能。具体依赖内容如下:
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.8.5</version>
</dependency>
6.2 组件的定义
实现第一个组件
@Component("a")
//@LiteflowComponent(id = "a", name = "A组件") 也可使用该注解,给组件起别名,方便日志查看
public class ACmp extends NodeComponent {
private static final Logger log = LoggerFactory.getLogger(ACmp.class);
@Override
public void process() {
log.info("执行了A组件。。。");
}
}
以此类推再分别定义b,c组件,Bcmp和CCmp。在这里,不再详细列出。
6.3 SpringBoot配置文件
然后,在SpringBoot的application.properties
里添加如下配置(application.yml
内容类似):
liteflow.rule-source=config/flow.el.xml
6.4 定义规则文件
同时,你得在resources下的config/flow.el.xml
中定义规则:
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(a, b, c);
</chain>
</flow>
6.5 编写SpringBoot启动代码
@SpringBootApplication
public class MessageDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MessageDemoApplication.class, args);
}
}
编写SpringBoot启动时运行代码:
@Component
public class ChainExecute implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(ChainExecute.class);
@Resource
private FlowExecutor flowExecutor;
@Override
public void run(String... args) throws Exception {
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "agrs");
}
}
6.6 执行第一个编排程序
运行MessageDemoApplication
打印出下面日志(为了方便查看,日志经过整理):
INFO ACmp: 执行了A组件。。。
INFO BCmp: 执行了B组件。。。
INFO CCmp: 执行了C组件。。。
6.7 调整编排顺序
将编排顺序调整从THEN(a, b, c)
调整为THEN(a, c, b)
运行,查看日志内容如下:
INFO ACmp: 执行了A组件。。。
INFO CCmp: 执行了C组件。。。
INFO BCmp: 执行了B组件。。。
7、组件讲解
在LiteFlow里,目前包含普通组件、选择组件 和 条件组件。
普通组件需要继承 NodeComponent
类、选择组件需要继承 NodeSwitchComponent
、条件组件需要继承 NodeIfComponent
。在第6节里,我们主要讲解了普通组件,这里不再做示例讲解。
7.1 组件方法
可覆盖方法
- isAccess
推荐实现isAccess
方法,表示是否进入该节点,可以用于业务参数的预先判断
- isContinueOnError
表示出错是否继续往下执行下一个组件,默认为false
- isEnd
如果覆盖后,返回true,则表示在这个组件执行完之后立马终止整个流程。对于这种方式,由于是用户主动结束的流程,属于正常结束,所以最终的isSuccess是为true的。
- beforeProcess和afterProcess
流程的前置和后置处理器,其中前置处理器,在isAccess
之后执行。
- onSuccess和onError
流程的成功失败事件回调
this关键字可以调用的方法
在组件节点里,随时可以通过方法this.getContextBean(clazz)
获取当前你自己定义的上下文,从而可以获取任何数据。
-
getNodeId
获取组件ID。 -
getName
获取组件别名。 -
getChainName
获取当前执行的流程名称。 -
getRequestData
获取流程的初始参数。 -
setIsEnd
表示是否立即结束整个流程 ,用法为this.setIsEnd(true)。对于这种方式,由于是用户主动结束的流程,属于正常结束,所以最终的isSuccess是为true的。 -
getTag
获取这个组件的标签信息。 -
invoke和invoke2Response
调用隐式流程。
7.2 选择组件
在实际业务中,往往要通过动态的业务逻辑判断到底接下去该执行哪一个节点,这就引申出了选择节点,选择节点可以用于SWITCH
关键字中。
将组件A改造成以下代码:
@Component("a")
//@LiteflowComponent(id = "a", name = "A组件")
public class ACmp extends NodeSwitchComponent {
private static final Logger log = LoggerFactory.getLogger(ACmp.class);
@Override
public String processSwitch() throws Exception {
log.info("执行了选择A组件。。。");
String para = this.getRequestData();
if (para != null) {
return "c";
} else {
return "b";
}
}
}
表达式调整为:
<chain name="chain1">
SWITCH(a).to(b, c);
</chain>
重新执行后,运行结果如下:
INFO ACmp: 执行了选择A组件。。。
INFO CCmp: 执行了C组件。。。
7.3 条件组件
条件组件,也可以称之为IF组件,返回是一个true/false。可用于IF...ELIF...ELSE
等关键字。
添加XCmp
组件,代码如下:
@Component("x")
public class XCmp extends NodeIfComponent {
private static final Logger log = LoggerFactory.getLogger(XCmp.class);
@Override
public boolean processIf() throws Exception {
log.info("执行了条件X组件。。。");
return true;
}
}
表达式调整为:
<chain name="chain1">
IF(x, a, b);
</chain>
执行程序,运行结果如下:
INFO XCmp:执行了条件X组件。。。
INFO ACmp: 执行了选择A组件。。。
我们发现,在执行选择A组件后,并没出现7.2的执行结果,可以调整EL表达式,达到预期结果。
<chain name="chain1">
IF(x, SWITCH(a).to(b, c), b);
</chain>
再次执行:
INFO XCmp:执行了条件X组件。。。
INFO ACmp: 执行了选择A组件。。。
INFO CCmp: 执行了C组件。。。
8、EL规则
在上面的示例中,我们体验到规则表达式的强大。一切复杂的流程在LiteFlow表达式的加持下,都异常丝滑简便。我们只需要很短的时间即可学会如何写一个很复杂流程的表达式。
8.1 串行编排
如果你要依次执行a,b,c,d四个组件,你可以用THEN
关键字,需要注意的是,THEN
必须大写。
<chain name="chain1">
THEN(a, b, c, d);
</chain>
图示为:
8.2 并行编排
使用用WHEN
将a,b,c并行编排。
<chain name="chain1">
WHEN(a, b, c);
</chain>
然后,我们使用 THEN
和 WHEN
进行混合编排。
<chain name="chain1">
THEN(a, WHEN(b, c, d), e);
</chain>
图示为: