今天给大家介绍一款轻量、快速、稳定可编排的组件式规则引擎框架LiteFlow。
在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护的成本得就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现。
LiteFlow为解耦逻辑而生,为编排而生,在使用LiteFlow之后,你会发现打造一个低耦合,灵活的系统会变得易如反掌!
如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个轻量,快速的组件式规则引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件,并支持热加载规则配置,实现即时修改。
使用LiteFlow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。这样,所有的组件,都能按照你的规则配置去进行复杂的流转。
LiteFlow是基于工作台模式进行设计的,何谓工作台模式?
n个工人按照一定顺序围着一张工作台,按顺序各自生产零件,生产的零件最终能组装成一个机器,每个工人只需要完成自己手中零件的生产,而无需知道其他工人生产的内容。每一个工人生产所需要的资源都从工作台上拿取,如果工作台上有生产所必须的资源,则就进行生产,若是没有,就等到有这个资源。每个工人所做好的零件,也都放在工作台上。
这个模式有几个好处:
这个模式映射到LiteFlow框架里,工人就是组件,工人坐的顺序就是流程配置,工作台就是上下文,资源就是参数,最终组装的这个机器就是这个业务。正因为有这些特性,所以LiteFlow能做到统一解耦的组件和灵活的装配。
LiteFlow只做基于逻辑的流转,而不做基于角色任务的流转。如果你想做基于角色任务的流转,推荐使用flowable (opens new window),activiti (opens new window)这2个框架。
LiteFlow适用于拥有复杂逻辑的业务,比如说价格引擎,下单流程等,这些业务往往都拥有很多步骤,这些步骤完全可以按照业务粒度拆分成一个个独立的组件,进行装配复用变更。使用LiteFlow,你会得到一个灵活度高,扩展性很强的系统。因为组件之间相互独立,也可以避免改一处而动全身的这样的风险。
LiteFlow提供了liteflow-spring-boot-starter依赖包,提供自动装配功能。具体依赖内容如下:
<dependency>
<groupId>com.yomahubgroupId>
<artifactId>liteflow-spring-boot-starterartifactId>
<version>2.8.5version>
dependency>
实现第一个组件
@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。在这里,不再详细列出。
然后,在SpringBoot的application.properties
里添加如下配置(application.yml
内容类似):
liteflow.rule-source=config/flow.el.xml
同时,你得在resources下的config/flow.el.xml
中定义规则:
THEN(a, b, c);
@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");
}
}
运行MessageDemoApplication
打印出下面日志(为了方便查看,日志经过整理):
INFO ACmp: 执行了A组件。。。
INFO BCmp: 执行了B组件。。。
INFO CCmp: 执行了C组件。。。
将编排顺序调整从THEN(a, b, c)
调整为THEN(a, c, b)
运行,查看日志内容如下:
INFO ACmp: 执行了A组件。。。
INFO CCmp: 执行了C组件。。。
INFO BCmp: 执行了B组件。。。
在LiteFlow里,目前包含普通组件、选择组件 和 条件组件。
普通组件需要继承 NodeComponent
类、选择组件需要继承 NodeSwitchComponent
、条件组件需要继承 NodeIfComponent
。在第6节里,我们主要讲解了普通组件,这里不再做示例讲解。
可覆盖方法
推荐实现isAccess
方法,表示是否进入该节点,可以用于业务参数的预先判断
表示出错是否继续往下执行下一个组件,默认为false
如果覆盖后,返回true,则表示在这个组件执行完之后立马终止整个流程。对于这种方式,由于是用户主动结束的流程,属于正常结束,所以最终的isSuccess是为true的。
流程的前置和后置处理器,其中前置处理器,在isAccess
之后执行。
流程的成功失败事件回调
this关键字可以调用的方法
在组件节点里,随时可以通过方法this.getContextBean(clazz)
获取当前你自己定义的上下文,从而可以获取任何数据。
getNodeId
获取组件ID。
getName
获取组件别名。
getChainName
获取当前执行的流程名称。
getRequestData
获取流程的初始参数。
setIsEnd
表示是否立即结束整个流程 ,用法为this.setIsEnd(true)。对于这种方式,由于是用户主动结束的流程,属于正常结束,所以最终的isSuccess是为true的。
getTag
获取这个组件的标签信息。
invoke和invoke2Response
调用隐式流程。
在实际业务中,往往要通过动态的业务逻辑判断到底接下去该执行哪一个节点,这就引申出了选择节点,选择节点可以用于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";
}
}
}
表达式调整为:
SWITCH(a).to(b, c);
重新执行后,运行结果如下:
INFO ACmp: 执行了选择A组件。。。
INFO CCmp: 执行了C组件。。。
条件组件,也可以称之为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;
}
}
表达式调整为:
IF(x, a, b);
执行程序,运行结果如下:
INFO XCmp:执行了条件X组件。。。
INFO ACmp: 执行了选择A组件。。。
我们发现,在执行选择A组件后,并没出现7.2的执行结果,可以调整EL表达式,达到预期结果。
IF(x, SWITCH(a).to(b, c), b);
再次执行:
INFO XCmp:执行了条件X组件。。。
INFO ACmp: 执行了选择A组件。。。
INFO CCmp: 执行了C组件。。。
在上面的示例中,我们体验到规则表达式的强大。一切复杂的流程在LiteFlow表达式的加持下,都异常丝滑简便。我们只需要很短的时间即可学会如何写一个很复杂流程的表达式。
如果你要依次执行a,b,c,d四个组件,你可以用THEN
关键字,需要注意的是,THEN
必须大写。
THEN(a, b, c, d);
图示为:
使用用WHEN
将a,b,c并行编排。
WHEN(a, b, c);
然后,我们使用 THEN
和 WHEN
进行混合编排。
THEN(a, WHEN(b, c, d), e);
图示为:
在以上示例里,b,c,d默认并行都执行完毕后,才会执行e。
我们在写业务逻辑的时候,通常会碰到选择性问题,即,如果返回结果1,则进入A流程,如果返回结果2,则进入B流程,如果返回结果3,则进入C流程。这时,我们可以用SWITCH
…to
的组合关键字,注意的是SWITCH
必须大写,to
小写。
SWITCH(a).to(b, c, d);
图示为:
条件编排是选择编排一个变种,选择编排是根据逻辑去选择多个子项中的一项。而条件编排只有真和假2个子项,这处理某些业务的过程中非常有用。简单理解,条件编排就是变成语言中的if else。
THEN(
IF(x, a),
b
);
图示为:
我们还可以通过IF编排一个三元运算:
THEN(
IF(x, a, b),
c
);
图示为:
使用ELSE表达式
IF(x1, a).ELIF(x2, b).ELIF(x3, c);
图示为:
接下来,我们再看一下复杂的流程
上面的图可以写成以下规则表达式:
THEN(
a, b,
WHEN(
THEN(c, WHEN(j, k)),
d,
THEN(h, i)
),
SWITCH(X).to(
m,
n
),
z
);
以上编排,多少会让人感觉有点复杂,还容易出现问题。我们可以通过子流程,进行重新编排:
THEN(
a, b,
WHEN(chain1, d, chain2),
chain3,
z
);
THEN(c, WHEN(j, k));
THEN(h, i);
SWITCH(X).to(m, n),
针对8.5的流程,我们还可以通过子变量进行重新编排:
t1 = THEN(c, WHEN(j, k));
t2 = THEN(h, i);
t3 = SWITCH(X).to(m, n);
THEN(
a, b,
WHEN(t1, d, t2),
t3,
z
);
平时我们写程序时,A调用B,那A一定要把B所需要的参数传递给B,要做到可编排,一定是消除每个组件差异性的。如果每个组件出参入参都不一致,那就没法编排了。
在LiteFlow框架体系中,每个组件的定义中是不需要接受参数的,也无任何返回的。每个组件只需要从数据上下文中获取自己关心的数据即可,而不用关心此数据是由谁提供的,同样的,每个组件也只要把自己执行所产生的结果数据放到数据上下文中即可,也不用关心此数据到底是提供给谁用的。这样一来,就从数据层面一定程度的解耦了。从而达到可编排的目的。
接下来,我们看一下LiteFlow的源码,了解下数据上下文处理方式:
public LiteflowResponse execute2Resp(String chainId, Object param) {
return this.execute2Resp(chainId, param, DefaultContext.class);
}
public LiteflowResponse execute2Resp(String chainId, Object param, Class... contextBeanClazzArray) {
return this.execute2Resp(chainId, param, contextBeanClazzArray, null, null, InnerChainTypeEnum.NONE);
}
public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) {
return this.execute2Resp(chainId, param, null, contextBeanArray, null, InnerChainTypeEnum.NONE);
}
所以,在整个流程中,我们可以这么传:
flowExecutor.execute2Resp("chain1", 流程初始参数);
也可以这么传:
flowExecutor.execute2Resp("chain1", 流程初始参数, CustomContext.class);
也可以这么传:
flowExecutor.execute2Resp("chain1", 流程初始参数, OrderContext.class, UserContext.class, SignContext.class);
还是不开心的话,那就这么传:
OrderContext orderContext = new OrderContext();
orderContext.setOrderNo("abcdefg");
flowExecutor.execute2Resp("chain1", null, orderContext);
LiteFlow还有很多高级特性,比如隐式流程啊,事件回调啊,声明式组件,组件切面啊,步骤信息,线程池的自定义,私有投递,还有简单监控。这款国产规则引擎快,在逻辑解偶这块,是要玩出花的节奏!
目前LiteFlow流程编排支持XML、Json和Yaml文件,也提供了zk对规则文件的解析处理。如果想用数据进行规则编排的,可以通过代码动态编排。从官方的性能测试来看,整体表现也不俗。