liteflow的业务逻辑编排能力是非常强,同时也是非常好用的。支持很多种方式的编排,串行编排、并行编排、选择编排、条件编排、循环编排,同时支持使用子流程、使用子变量等等。本文参考liteflow官网,学习liteflow的执行流程,官网对学习liteflow非常友好。
liteflow的开源地址:https://gitee.com/dromara/liteFlow,很感谢作者铂赛东开源这么好用的业务编排框架。
一、liteflow的使用
首先引入liteflow的spring-boot-starter依赖,然后继承 NodeComponent,重写process方法。在application.yml中配置xml中配置资源规则,以说明当前串行器的执行顺序。
比如当前的执行顺序是a->b->c
<?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")。
@Component public class YourClass{ @Resource private FlowExecutor flowExecutor; public void testConfig(){ LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); } }
完成编写后,即可执行,执行的顺序a->b->c串行编排。也即执行后可以看到我们想要的结果。
这样简化了我们组装业务逻辑的方式。
这么方便使用,是不是非常好奇怎么实现的呢?
二、如何实现
首先我们还是从example开始,从最基础的builder开始学习。
在脚本中,串行器以THEN开头,而并行器以WHEN开头。
@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)中的方法,也即在这里完成所有的主体逻辑。
主体的逻辑整体在:
com.yomahub.liteflow.core.FlowExecutor#doExecute
这个方法里面。
因此我们对3)进行着重查看和说明。
三、chain.execute(slotIndex)的实现
//获取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