liteflow的业务逻辑编排能力是非常强,同时也是非常好用的。支持很多种方式的编排,串行编排、并行编排、选择编排、条件编排、循环编排,同时支持使用子流程、使用子变量等等。本文参考liteflow官网,学习liteflow的执行流程,官网对学习liteflow非常友好。
liteflow的开源地址:https://gitee.com/dromara/liteFlow,很感谢作者铂赛东开源这么好用的业务编排框架。
一、liteflow的使用
首先引入liteflow的spring-boot-starter依赖,然后继承 NodeComponent,重写process方法。在application.yml中配置xml中配置资源规则,以说明当前串行器的执行顺序。
比如当前的执行顺序是a->b->c
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(a, b, c);
</chain>
</flow>
在业务系统的业务逻辑层注入FlowExecutor,chain1在业务逻辑执行对应的业务逻辑,也即这个顺序是a->b->c,也即 NodeComponent中的业务逻辑,Bean对应@Component("a")、@Component("b")、@Component("c")。
代码语言:javascript复制@Component
public class YourClass{
@Resource
private FlowExecutor flowExecutor;
public void testConfig(){
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
}
}
完成编写后,即可执行,执行的顺序a->b->c串行编排。也即执行后可以看到我们想要的结果。
这样简化了我们组装业务逻辑的方式。
这么方便使用,是不是非常好奇怎么实现的呢?
二、如何实现
首先我们还是从example开始,从最基础的builder开始学习。
在脚本中,串行器以THEN开头,而并行器以WHEN开头。
代码语言:javascript复制@Test
public void testBuilder() throws Exception {
// 基于liteflowNode构建器创建节点
LiteFlowNodeBuilder.createNode()
.setId("a")
.setName("组件A")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.ACmp")
.build();
LiteFlowNodeBuilder.createNode()
.setId("b")
.setName("组件B")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.BCmp")
.build();
LiteFlowNodeBuilder.createNode()
.setId("c")
.setName("组件C")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.CCmp")
.build();
LiteFlowNodeBuilder.createNode()
.setId("d")
.setName("组件D")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.DCmp")
.build();
LiteFlowNodeBuilder.createNode()
.setId("e")
.setName("组件E")
.setType(NodeTypeEnum.SWITCH)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.ECmp")
.build();
LiteFlowNodeBuilder.createNode()
.setId("f")
.setName("组件F")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.FCmp")
.build();
LiteFlowNodeBuilder.createNode()
.setId("g")
.setName("组件G")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.builder.cmp1.GCmp")
.build();
// 创建节点后,创建chain链路
LiteFlowChainELBuilder.createChain().setChainName("chain2").setEL("THEN(c, d)").build();
// 创建链路
LiteFlowChainELBuilder.createChain()
.setChainName("chain1")
.setEL("THEN(a, b, WHEN(SWITCH(e).to(f, g, chain2)))")
.build();
// 基于流程执行器执行链路1
LiteflowResponse response = flowExecutor.execute2Resp("chain1");
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("a[组件A]==>b[组件B]==>e[组件E]==>c[组件C]==>d[组件D]", response.getExecuteStepStr());
}
flowExecutor.execute2Resp("chain1")这个方法是业务逻辑执行的核心方法。但是在此之前会执行一些的方法:
1)LiteFlowNodeBuilder创建a到g组件节点。从代码中可以看到节点信息最终会添加到FlowBus中,使用了FlowBus.addNode(this.node.getId(), this.node.getName(), this.node.getType(), this.node.getClazz())。
2)LiteFlowChainELBuilder.createChain()创建chain的过程build中可以看到最终将数据也添加到了Chain中之外,还将chain添加到了chainMap中。
3)完成后,通过流程执行器完成请求 flowExecutor.execute2Resp("chain1")。
根据debug的信息,可以看到LiteflowExecutorInit实现了InitializingBean,重写afterPropertiesSet方法,此时会对flowExecutor做一次初始化,执行初始化Cmp,对应ruleResource进行判断,如果LiteflowConfig不存在配置信息,则加载SPI中,通过类加载器获取,如果存在,则加载,同时放入到liteflowConfig中,否则进行返回。
也即此时执行单元测试基于 flowExecutor.execute2Resp("chain1")进行执行后续的逻辑,也即3)中的方法,也即在这里完成所有的主体逻辑。
主体的逻辑整体在:
代码语言:javascript复制com.yomahub.liteflow.core.FlowExecutor#doExecute
这个方法里面。
因此我们对3)进行着重查看和说明。
三、chain.execute(slotIndex)的实现
代码语言:javascript复制 //获取chain,根据chainId获取
Chain chain = null;
try {
chain = FlowBus.getChain(chainId);
if (ObjectUtil.isNull(chain)) {
String errorMsg = StrUtil.format("couldn't find chain with the id[{}]", chainId);
throw new ChainNotFoundException(errorMsg);
}
// 执行chain,也即设置槽id 对应chainId
chain.execute(slotIndex);
}
1)从chain到node的过程
可以看到首先会拿到chain,因为之前我们在2)完成了chain数据的填充,将将chain数据放入到了chainMap中。因此此时必然可以拿到chain的信息,然后基于chain调用节点执行器执行操作chain.execute(slotIndex)。Condition执行condition操作。当前所在的ChainName 如果对于子流程来说,那这个就是子流程所在的Chain。可以看到此时的executeCondition会执行对应的condition对应的chain。这里以串行器为例子进行说明,因此此时会进入到ThenCondition中。可以看到ThenConditon的executeCondition中存在前置preCondition.execute(slotIndex)、具体的处理executableItem.execute(slotIndex)、finally处理finallyCondition.execute(slotIndex)。此时可以看到对应的方法有三个:chain、condition、node。可以看到此时会执行节点node操作,因为前两者已经执行过。
2)node执行逻辑
设置槽索引和引用节点。,如果当前的实例可以access,则获取节点执行器,然后执行节点执行器。来到我们的执行器入口方法com.yomahub.liteflow.flow.executor.NodeExecutor#execute。这个方法可以看到,分为前置、执行中、后置方法。作者想得非常的周到,留下了扩展。除此之外,作者还给我们设置了失败重试的方法。节点组件的核心方法com.yomahub.liteflow.core.NodeComponent#execute。
在这个方法里面,会创建cmpStep对象,然后设置tag、step的信息。然后执行前置、具体的业务逻辑处理self.process(),此时会业务系统的组件方法中,也即我们自己业务系统写的NodeComponent方法,执行完成会执行成功的回调方法,如果出现异常,则设置step信息,然后执行失败回调方法。最终进行后置方法设置时间消耗。将指标统计信息添加到monitorBus中。
3)执行顺序与test中的设置对应关系
也即从这里可以看到首先会执行chain.execute(slotIndex)->executableItem.execute(slotIndex)->NodeComponent#execute,也即先执行chain,然后chain到对应的condition,最终到对应的node。也即应证了上面设置的顺序。先节点,然后condition,最终到chain。
四、实现的逻辑,可以总结如下UML
参考:https://gitee.com/dromara/liteFlow
https://liteflow.cc/