SpringBoot + LiteFlow:轻松应对复杂业务逻辑,简直不要太香! ...

打印 上一主题 下一主题

主题 884|帖子 884|积分 2652


LiteFlow简介

LiteFlow是什么?

LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑。通过支持热加载规则配置,开发者能够即时调整流程步骤,将复杂的业务如价格计算、下单流程等拆分为独立且可复用的组件,从而实现系统的高度灵活性与扩展性,避免了牵一发而动全身的问题。旨在优化开发流程,减少冗余工作,让团队能够更聚焦于核心业务逻辑,而将流程控制层面的重任托付给该框架进行自动化处理。
LiteFlow整合了流程编排与规则引擎的核心特性,提供XML、JSON或YAML格式的灵活流程定义,以及本地文件系统、数据库、ZooKeeper、Nacos、Apollo、Redis等多种规则文件存储方案。其内建插件如liteflow-rule-nacos,以及开放的扩展机制,赋予开发人员自定义规则解析器的能力,满足多样化场景下的规则管理需求。
对于基于角色任务流转的场景,LiteFlow并非最佳选择,推荐使用Flowable或Activiti等专门的工作流引擎。
LiteFlow的架构

LiteFlow是从获取上下文开始的,这个上下文通常包含了执行流程所需的数据和环境信息。通过解析这些上下文数据,LiteFlow能够理解并执行对应的规则文件,驱动业务流程的执行。在LiteFlow中,业务流程被组织成一系列的链路(或节点),每个链路代表一个业务步骤或决策点。这些链路上的节点,也就是业务组件,是独立的,可以支持多种脚本语言,如Groovy、JavaScript、Python、Lua等,以便根据具体业务需求进行定制。下图为LiteFlow的整体架构图。

LiteFlow的作用


  • LiteFlow将瀑布式代码进行组件化、灵活的编排体系,组件可独立调整替换,规则引擎语法简单易学。
    利用LiteFlow可以把传统的瀑布式代码重构为以组件为中心的概念体系,从而获得灵活的编排能力。在这种结构里,各个组件彼此分离,允许轻松调整和替换。组件本身可通过脚本定制,而且组件间的过渡完全受规则引导。此外,LiteFlow具备简单易懂的DSL规则引擎语法,能快速入门掌握。

  • LiteFlow强大的编排能力
    LiteFlow的编排语法强大到可以编排出任何你想要的逻辑流程。如下图复杂的语法,如果使用瀑布式的代码去写,那种开发以及维护难度可想而知,但是使用LiteFlow你可以轻松完成逻辑流程的编排,易于维护。

  • LiteFlow支持组件热部署
    通过LiteFlow,你可以实现组件的实时热替换,同时也能在已有的逻辑流程中随时插入新的组件,以此动态调整你的业务逻辑。

LiteFlow的环境支持


  • JDK
    LiteFlow要求的最低的JDK版本为8,支持JDK8~JDK17所有的版本。当然如果使用JDK11以上,确保LiteFlow的版本为v2.10.6及其以上版本。
如果你使用JDK11及其以上的版本,请确保jvm参数加上以下参数:--add-opens java.base/sun.reflect.annotation=ALL-UNNAMED


  • Spring
    LiteFlow要求的Spring的最低版本为Spring 5.0。支持的范围是Spring 5.X ~ Spring 6.X。
  • SpringBoot
    LiteFlow要求的Springboot的最低的版本是2.0。支持的范围是Springboot 2.X ~ Springboot 3.X。
LiteFlow的性能

LiteFlow框架在启动时完成大部分工作,包括解析规则、注册组件和组装元信息,执行链路时对系统资源消耗极低。在设计之初就注重性能表现,对核心代码进行了优化。
实际测试中,LiteFlow表现出色,50多个业务组件组成的链路在压测中单点达到1500 TPS,成功应对双11、明星顶流带货等大规模流量挑战。
尽管LiteFlow框架自身性能卓越,但实际执行效率取决于业务组件的性能。若组件包含大量循环数据库查询、不良SQL 或大量RPC同步调用,整体TPS也会较低。但这归咎于业务组件的性能问题,而非LiteFlow框架本身的性能问题。整体系统吞吐量的高低不只依赖于某个框架,而是需要整体优化业务代码才能提升。
数据来源于LiteFlow官方文档说明。
LiteFlow使用

以下我们结合SpringBoot环境使用。
LiteFlow在使用上可以按照引入依赖,LiteFlow相关配置,规则文件,定义组件,节点编排,执行流程进行。
引入依赖
  1. <dependency>  
  2.         <groupId>com.yomahub</groupId>  
  3.         <artifactId>liteflow-spring-boot-starter</artifactId>  
  4.         <version>2.11.1</version>  
  5. </dependency>
复制代码
目前liteflow的稳定版本已经更新到2.11.4.2。本文依托于2.11.1做讲解演示。好多新的功能均在2.9.0以后的版本中才有。
配置项

LiteFlow有诸多配置项,大多数配置项有默认值,可以不必配置,同时官方也建议某个配置项不了解它有什么用时,就不要去随意的改它的值。
  1. liteflow:
  2.   #规则文件路径
  3.   rule-source: config/flow.el.xml
  4.   #-----------------以下非必须-----------------
  5.   #liteflow是否开启,默认为true
  6.   enable: true
  7.   #liteflow的banner打印是否开启,默认为true
  8.   print-banner: true
  9.   #zkNode的节点,只有使用zk作为配置源的时候才起作用,默认为/lite-flow/flow
  10.   zk-node: /lite-flow/flow
  11.   #上下文的最大数量槽,默认值为1024
  12.   slot-size: 1024
  13.   #FlowExecutor的execute2Future的线程数,默认为64
  14.   main-executor-works: 64
  15.   #FlowExecutor的execute2Future的自定义线程池Builder,LiteFlow提供了默认的Builder
  16.   main-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultMainExecutorBuilder
  17.   #自定义请求ID的生成类,LiteFlow提供了默认的生成类
  18.   request-id-generator-class: com.yomahub.liteflow.flow.id.DefaultRequestIdGenerator
  19.   #并行节点的线程池Builder,LiteFlow提供了默认的Builder
  20.   thread-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultWhenExecutorBuilder
  21.   #异步线程最长的等待时间(只用于when),默认值为15000
  22.   when-max-wait-time: 15000
  23.   #异步线程最长的等待时间(只用于when),默认值为MILLISECONDS,毫秒
  24.   when-max-wait-time-unit: MILLISECONDS
  25.   #when节点全局异步线程池最大线程数,默认为16
  26.   when-max-workers: 16
  27.   #并行循环子项线程池最大线程数,默认为16
  28.   parallelLoop-max-workers: 16
  29.   #并行循环子项线程池等待队列数,默认为512
  30.   parallelLoop-queue-limit: 512
  31.   #并行循环子项的线程池Builder,LiteFlow提供了默认的Builder
  32.   parallelLoop-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultParallelLoopExecutorBuilder
  33.   #when节点全局异步线程池等待队列数,默认为512
  34.   when-queue-limit: 512
  35.   #是否在启动的时候就解析规则,默认为true
  36.   parse-on-start: true
  37.   #全局重试次数,默认为0
  38.   retry-count: 0
  39.   #是否支持不同类型的加载方式混用,默认为false
  40.   support-multiple-type: false
  41.   #全局默认节点执行器
  42.   node-executor-class: com.yomahub.liteflow.flow.executor.DefaultNodeExecutor
  43.   #是否打印执行中过程中的日志,默认为true
  44.   print-execution-log: true
  45.   #是否开启本地文件监听,默认为false
  46.   enable-monitor-file: false
  47.   #是否开启快速解析模式,默认为false
  48.   fast-load: false
  49.   #简易监控配置选项
  50.   monitor:
  51.     #监控是否开启,默认不开启
  52.     enable-log: false
  53.     #监控队列存储大小,默认值为200
  54.     queue-limit: 200
  55.     #监控一开始延迟多少执行,默认值为300000毫秒,也就是5分钟
  56.     delay: 300000
  57.     #监控日志打印每过多少时间执行一次,默认值为300000毫秒,也就是5分钟
  58.     period: 300000
复制代码
只要使用规则,则必须配置rule-source配置,但是如果你是用代码动态构建规则,则rule-source自动失效。
规则文件

从上面LiteFlow的整体架构图中可以看出LiteFlow支持多种规则文件源配置:本地文件,数据库,zk,Nacos,Apollo,Etcd,Redis以及自定义配置源。本文将会以本地规则文件为例讲解,其余配置源将在后续文章中讲解实时修改流程中在进行分享,
LiteFlow支持3种规则文件格式:XML,JSON,YML,3种文件的配置相差无几。LiteFlow的组成很轻量,主要由Node以及Chain元素构成。值得一提的是:如果在非Spring环境下,Node节点是必须的,配置配置,否则会导致报错找不到节点。当然在Spring环境下,我们可以不必配置Node节点,只需要将相应的节点注册到Spring上下文即可。
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <flow>  
  3.         <chain name="chain1">  
  4.                 THEN(a, b, c);  
  5.         </chain>  
  6.           
  7.         <chain name="scChain">  
  8.                 SWITCH(s1).to(s2, THEN(a,b).id("d"));  
  9.         </chain>
  10. </flow>       
复制代码
组件

在介绍具体的组件之前,我们先来了解下@LiteflowComponent注解。
  1. @Target({ ElementType.TYPE })  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Documented  
  4. @Inherited  
  5. @Component  
  6. public @interface LiteflowComponent {  
  7.         @AliasFor(annotation = Component.class, attribute = "value")  
  8.         String value() default "";  
  9.         @AliasFor(annotation = Component.class, attribute = "value")  
  10.         String id() default "";
  11.         /**
  12.         * 可以给节点起别名
  13.         **/
  14.         String name() default "";  
  15.   
  16. }
复制代码
@LiteflowComponent继承自@Component注解,在Spring环境中,可以将组件注入到容器中。它的value或者id即对应规则文件中的node的id。例如上述规则文件中的a,b,c等。
普通组件:NodeComponent

普通组件节点需要继承NodeComponent,需要实现process方法。可用于THEN和WHEN编排中。
  1. @LiteflowComponent("a")  
  2. public class AComponent extends NodeComponent {  
  3.   
  4.         @Override  
  5.         public void process() throws Exception {  
  6.                 System.out.println("执行A规则");  
  7.         }  
  8. }
复制代码
当然NodeComponent中还有一些其他方法可以重写,以达到自己的业务需求。例如:

  • isAccess():表示是否进入该节点,可以用于业务参数的预先判断。
  • isContinueOnError():表示出错是否继续往下执行下一个组件,默认为false
  • isEnd():是否结束整个流程(不往下继续执行)。
    如果返回true,则表示在这个组件执行完之后立马终止整个流程。此时由于是用户主动结束的流程,属于正常结束,所以流程结果中(LiteflowResponse)的isSuccess是true。
  • beforeProcess()和afterProcess():流程的前置和后置处理器,其中前置处理器,在isAccess 之后执行。
  • onSuccess()和onError():流程的成功失败事件回调
  • rollback():流程失败后的回滚方法。
在任意组件节点的内部,还可以使用this关键字调用对应的方法:

  • 获取流程初始入参参数
    我们在组件节点内部可以通过this.getRequestData()去获取流程初始的入参。例如:
  1. @LiteflowComponent("a")  
  2. public class AComponent extends NodeComponent {  
  3.   
  4.         @Override  
  5.         public void process() throws Exception {  
  6.         DataRequest dataRequest = this.getRequestData();  
  7.                 System.out.println("执行A规则");  
  8.         }  
  9. }
复制代码

  • 获取上下文
    在组件节点里,随时可以通过方法this.getContextBean(clazz)获取当前你自己定义的上下文,从而可以获取到上下文的数据。例如:
  1. @LiteflowComponent("a")  
  2. public class AComponent extends NodeComponent {  
  3.   
  4.         @Override  
  5.         public void process() throws Exception {  
  6.                 ConditionContext context = this.getContextBean(ConditionContext.class);  
  7.                 System.out.println("执行A规则");  
  8.         }  
  9. }
复制代码

  • setIsEnd
    是否立即结束整个流程 ,用法为this.setIsEnd(true)。
    还有一些其他的方法,可以参考源码。
选择组件:NodeSwitchComponent

实际业务中,我们针对不同的业务类型,有不同的业务处理逻辑,例如上一篇文章中的订单类型一样,此时就需要节点动态的判断去执行哪些节点或者链路,所以就出现了选择组件。
选择组件需要实现NodeSwitchComponent,并且需要实现processSwitch()方法。用于SWITCH编排中。
processSwitch()方法返回值是一个String,即下一步流程执行的节点ID或者链路tag。
  1. @LiteflowComponent("s)
  2. public class SwitchComponent extends NodeSwitchComponent {  
  3.   
  4.         @Override  
  5.         public String processSwitch() throws Exception {  
  6.                 System.out.println("执行switch规则");  
  7.                 return "a";  
  8.         }  
  9. }
复制代码
规则文件中,配置的SWITCH编排信息为:
  1. <chain name="scChain">  
  2.         SWITCH(s).to(a, b, c);  
  3. </chain>
复制代码
此时s节点就会返回要执行的节点id为a,即要执行a流程。通常switch的节点的逻辑我们需要具体结合业务类型,例如订单类型枚举去使用。
除了可以返回id以外,我们还可以返回tag(标签)。例如我们在规则文件中这么写:
在规则表达式中我们可以这样使用:
  1. <chain name="scChain">  
  2.         SWITCH(s).to(a.tag("td"), b.tag("td"), c.tag("td));  
  3. </chain>
复制代码
然后在SWITCH中返回tag:
  1. @LiteflowComponent("s)
  2. public class SwitchComponent extends NodeSwitchComponent {  
  3.   
  4.         @Override  
  5.         public String processSwitch() throws Exception {  
  6.                 System.out.println("执行switch规则");  
  7.                 return ":td"      // 进入 b 节点,含义:选择第一个标签为td的节点
  8.                 return "tag:td"   // 进入 b 节点,含义:选择第一个标签为td的节点
  9.                 return "a";       // 进入 b 节点,含义:选择targetId是b的节点
  10.                 return "b:";      // 进入 b 节点,含义:选择第一个targetId是b的节点
  11.                 return "b:td";    // 进入 b 节点,含义:选择targetId是b且标签是td的节点
  12.                 return ":";       // 进入 b 节点,含义:选择第一个节点
  13.                 return "d";       // 进入 d 节点,含义:选择targetId是d的节点
  14.                 return "d:";      // 进入 d 节点,含义:选择第一个targetId是d的节点
  15.                 return "d:td";    // 进入 d 节点,含义:选择targetId是d且标签是td的节点
  16.                 return "b:x";     // 报错,原因:没有targetId是b且标签是x的节点
  17.                 return "x";       // 报错,原因:没有targetId是x的节点
  18.                 return "::";      // 报错,原因:没有找到标签是":"的节点
  19.         }  
  20. }
复制代码
NodeSwitchComponent继承至NodeComponent,其节点的内部可以覆盖的方法和this关键字NodeComponent。
条件组件:NodeForComponent

条件组件,也是IF组件,返回值是一个boolean。需要继承NodeForComponent,实现processIf()方法。可用于IF...ELIF...ELSE编排。例如:
  1. <chain name = "ifChain">  
  2.         IF(x, a, b);  
  3. </chain>
复制代码
该例中x就是一个条件组件,如果x返回true,则会执行a节点,否则执行b节点。
  1. @LiteflowComponent("x")  
  2. public class IfXComponent extends NodeIfComponent {  
  3.   
  4.         @Override  
  5.         public boolean processIf() throws Exception {  
  6.                 System.out.println("执行X节点");  
  7.                 return false;  
  8.         }  
  9. }
复制代码
NodeIfComponent继承至NodeComponent,其节点内部可以覆盖的方法和this关键字NodeComponent。
次数循环组件:NodeForComponent

次数循环组件。返回的是一个int值的循环次数。继承NodeForComponent,实现processFor()方法, 主要用于FOR...DO...表达式。在紧接着DO编排中的节点中,可以通过this.getLoopIndex()获取下标信息,可以从对应数组或者集合中通过下表获取对应的元素信息。
  1. <chain name = "forChain">  
  2.         FOR(f).DO(a);  
  3. </chain>
复制代码
  1. @LiteflowComponent("f")  
  2. public class ForComponent extends NodeForComponent {  
  3.   
  4.         @Override  
  5.         public int processFor() throws Exception {  
  6.                 DataContext dataContext = this.getContextBean(DataContext.class);  
  7.                 List<String> dataList = dataContext.getDataList();  
  8.                 return dataList.size();  
  9.         }  
  10. }
  11. @LiteflowComponent("a")  
  12. public class AComponent extends NodeComponent {  
  13.   
  14.         @Override  
  15.         public void process() throws Exception {  
  16.                 Integer loopIndex = this.getLoopIndex();  
  17.                 DataContext dataContext = this.getContextBean(DataContext.class);  
  18.                 List<String> dataList = dataContext.getDataList();  
  19.                 String str = dataList.get(loopIndex);  
  20.                 System.out.println("执行A规则:"+str);  
  21.         }  
  22. }
复制代码
其中f组件相当于定义一个数组或者集合的元素个数,类似
[code]for(int i=0;i processIterator() throws Exception {                  DataContext dataContext = this.getContextBean(DataContext.class);                 return Optional.ofNullable(dataContext.getDataList())                        .orElse(Lists.newArrayList()).iterator();          }  }@LiteflowComponent("a")  public class AComponent extends NodeComponent {            @Override          public void process() throws Exception {                  String str = this.getCurrLoopObj();                  System.out.println("执行A规则:"+str);         }  }... contextBeanClazzArray)//第一个参数为流程ID,第二个参数为流程入参,后面可以传入多个上下文的Beanpublic LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray)// 第一个参数为流程ID,第二个参数为流程入参,第三个参数是用户的RequestId,后面可以传入多个上下文的Beanpublic LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表