liteflow 规则引擎框架

1. 前言

在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护成本就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现

如何打破僵局?LiteFlow为解耦逻辑而生,为编排而生,在使用LiteFlow之后,你会发现打造一个低耦合,灵活的系统会变得易如反掌!

LiteFlow是一个非常强大的现代化的规则引擎框架,融合了编排特性和规则引擎的所有特性

-> LiteFlow官网链接 <-

2. LiteFlow框架的优势

如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个编排式的规则引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件

利用LiteFlow,你可以将瀑布流式的代码,转变成以组件为核心概念的代码结构,这种结构的好处是可以任意编排,组件与组件之间是解耦的,组件可以用脚本来定义,组件之间的流转全靠规则来驱动。LiteFlow拥有开源规则引擎最为简单的DSL语法。十分钟就可上手

组件可实时热更替,也可以给编排好的逻辑流里实时增加一个组件,从而改变你的业务逻辑。

LiteFlow支持把编排规则和脚本放在数据库,注册中心中,还有可以任意扩展的接口,方便你定制。

2.1 LiteFlow的设计原则

LiteFlow是基于工作台模式进行设计的,何谓工作台模式?

n个工人按照一定顺序围着一张工作台,按顺序各自生产零件,生产的零件最终能组装成一个机器,每个工人只需要完成自己手中零件的生产,而无需知道其他工人生产的内容。每一个工人生产所需要的资源都从工作台上拿取,如果工作台上有生产所必须的资源,则就进行生产,若是没有,就等到有这个资源。每个工人所做好的零件,也都放在工作台上

这个模式有几个好处:

  • 每个工人无需和其他工人进行沟通。工人只需要关心自己的工作内容和工作台上的资源。这样就做到了每个工人之间的解耦和无差异性。
  • 即便是工人之间调换位置,工人的工作内容和关心的资源没有任何变化。这样就保证了每个工人的稳定性。
  • 如果是指派某个工人去其他的工作台,工人的工作内容和需要的资源依旧没有任何变化,这样就做到了工人的可复用性。
  • 因为每个工人不需要和其他工人沟通,所以可以在生产任务进行时进行实时工位更改:替换,插入,撤掉一些工人,这样生产任务也能实时的被更改。这样就保证了整个生产任务的灵活性。

个模式映射到LiteFlow框架里,工人就是组件,工人坐的顺序就是流程配置,工作台就是上下文,资源就是参数,最终组装的这个机器就是这个业务。正因为有这些特性,所以LiteFlow能做到统一解耦的组件和灵活的装配

2.2 LiteFlow使用场景

LiteFlow适用于拥有复杂逻辑的业务,比如说价格引擎,下单流程等,这些业务往往都拥有很多步骤,这些步骤完全可以按照业务粒度拆分成一个个独立的组件,进行装配复用变更。使用LiteFlow,你会得到一个灵活度高,扩展性很强的系统。因为组件之间相互独立,也可以避免改一处而动全身的这样的风险

3. 代码工程

liteFlow可以帮助我们快速编排复杂的业务规则,并实现动态的规则更新。

3.1 普通组件版

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>liteflow-spring-boot-starter</artifactId>
    <version>2.12.2.3</version>
    </dependency>
  2. 组件定义

    组件a

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import com.yomahub.liteflow.annotation.LiteflowComponent;
    import com.yomahub.liteflow.core.NodeComponent;

    /**
    * @author xiaoyuge
    */
    @LiteflowComponent("a")
    public class AComponent extends NodeComponent {
    @Override
    public void process() {
    System.out.println("-----a-------");
    }
    }

    组件b

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import com.yomahub.liteflow.annotation.LiteflowComponent;
    import com.yomahub.liteflow.core.NodeComponent;

    /**
    * @author xiaoyuge
    */
    @LiteflowComponent("b")
    public class BComponent extends NodeComponent {
    @Override
    public void process() {
    System.out.println("-----b-------");
    }
    }

    组件c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import com.yomahub.liteflow.annotation.LiteflowComponent;
    import com.yomahub.liteflow.core.NodeComponent;

    /**
    * @author xiaoyuge
    */
    @LiteflowComponent("c")
    public class CComponent extends NodeComponent {
    @Override
    public void process() {
    System.out.println("-----c-------");
    }
    }
  3. application.yml 配置文件

    1
    2
    liteflow:
    rule-source: classpath:flow.xml

    也可以自定配置类使用注解@LiteFLowConfig来指定配置文件

    1
    2
    3
    4
    @LiteflowConfig("liteflow.xml")
    public class YourLiteflowConfig {
    // ...
    }
  4. 规则文件定义

    1
    2
    3
    4
    5
    6
    <?xml version="1.0" encoding="UTF-8"?>
    <flow>
    <chain name="chain1">
    THEN(a, b, c);
    </chain>
    </flow>
  5. 启动类

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    public class LiteFlowDemoApplication {

    public static void main(String[] args) {
    SpringApplication.run(LiteFlowDemoApplication.class, args);
    }
    }
  6. 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package org.example;

    import com.yomahub.liteflow.core.FlowExecutor;
    import com.yomahub.liteflow.flow.LiteflowResponse;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;

    @RestController
    public class LiteFlowController {

    @Resource
    private FlowExecutor flowExecutor;

    @GetMapping("/index")
    public void index() throws ExecutionException, InterruptedException {
    final Future<LiteflowResponse> liteflowResponseFuture = flowExecutor.execute2Future("chain1", "arg");;
    System.out.println(liteflowResponseFuture.get());
    }
    }
  7. 启动并访问测试类,输出日志如下

    1
    2
    3
    4
    5
    -----a-------
    -----b-------
    -----c-------
    a<0>==>b<0>==>c<0>
    com.yomahub.liteflow.flow.LiteflowResponse@1fce8e14

3.2 进阶版

  1. 编写两个组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @LiteflowComponent("messageNode")
    public class MessageNode extends NodeComponent {

    @Override
    public void process() {
    Slot slot = this.getSlot();
    System.out.println("获取参数"+slot.getRequestData().toString());
    System.out.println("-----发送消息逻辑------");
    }
    }
    @LiteflowComponent("pointNode")
    public class PointNode extends NodeComponent {

    @Override
    public void process() {
    //获取参数
    Slot slot = this.getSlot();
    System.out.println("获取参数"+slot.getRequestData().toString());
    System.out.println("-----发放积分逻辑------");
    }
    }
  2. 编写规则文件

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <flow>
    <chain name="flow">
    <!--在同一个并行组中, then 和 when 来表示同步节点和异步节点。 -->
    THEN(WHEN(messageNode, pointNode));
    </chain>
    </flow>
  3. 流程触发

    1
    2
    3
    4
    5
    @GetMapping("/index")
    public void index() {
    //完成订单的其他逻辑,传递参数
    flowExecutor.execute2Resp("flow", "这是order的参数");
    }

4. 扩展

4.1 LiteFlow脚本组件

LiteFlow框架目前一共支持6种脚本语言:GroovyJavascriptQLExpressPythonLuaAviator

LiteFlow采用SPI机制进行选择脚本框架来动态编译脚本,官方推荐使用Groovy,因为和Java语法最接近,下面使用Groovy为例。

4.2 LiteFLow组件类别

LiteFlow中,主要有以下几种组件:

  • 普通组件:集成NodeComponent,用于执行具体的业务逻辑
    1
    2
    3
    4
    <!--关键字THEN,继承NodeComponent,实现process方法-->
    <chain name="chain1">
    THEN(a, b, c);
    </chain>
  • 选择组件:通过业务逻辑选择不同的执行路径
    1
    2
    3
    4
    <!-- 关键字SWITCH, 继承NodeSwitchComponent,实现processSwitch方法 -->
    <chain name="chain1">
    SWITCH(a).to(b, c);
    </chain>
  • 条件组件:基于条件返回结果,决定下一步的业务流程

    1
    2
    3
    4
    <!--关键字IF...ELIF...ELSE,继承NodeIfComponent,实现processIf方法,表达式如下-->
    <chain name="chain1">
    IF(x, a, b);
    </chain>
  • 次数循环组件:根据循环次数,执行某些普通组件

    1
    2
    3
    4
    <!--关键字FOR...DO...,继承NodeForComponent,实现processFor方法,表达式如下:-->
    <chain name="chain1">
    FOR(f).DO(THEN(a, b));
    </chain>
  • 条件循环组件:根据返回结果的true/false,来判断是否要执行某些普通组件
    1
    2
    3
    4
    <!--关键字WHILE...DO...,继承NodeWhileComponent,实现processWhile方法,表达式如下:-->
    <chain name="chain1">
    WHILE(w).DO(THEN(a, b));
    </chain>
  • 迭代循环组件:根据返回结果的true/false,来判断是否要执行某些普通组件

    1
    2
    3
    4
    <!--关键字ITERATOR...DO...,继承NodeIteratorComponent,实现processIterator方法,表达式如下:-->
    <chain name="chain1">
    ITERATOR(x).DO(THEN(a, b));
    </chain>
  • 退出循环组件:

    1
    2
    3
    4
    5
    6
    7
    8
    <!--关关键字BREAK,继承NodeBreakComponent,实现processBreak方法,主要用于以下组合:FOR...DO...BREAK,WHILE...DO...BREAK,ITERATOR...DO...BREAK-->
    <chain name="chain1">
    FOR(f).DO(THEN(a, b)).BREAK(c);
    </chain>

    <chain name="chain1">
    WHILE(w).DO(THEN(a, b)).BREAK(c);
    </chain>

4.3 脚本的定义

脚本组件也是定义在规则文件中的,以xml为例

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<nodes>
<node id="e" name="普通脚本" type="script" language="groovy">
<![CDATA[
println("groovy脚本wsss:main")
]]>
</node>
</nodes>
</flow>

这里定义了一个e节点,name为别名, type是节点的类型,language代表使用的语言。

type的分类:

  • script: 普通脚本节点,脚本无需返回
  • switch_script: 选择脚本节点,脚本里需要返回选择的节点ID
  • if_script: 条件脚本节点,脚本里需要返回true/false;
  • for_script: 数量循环节点,脚本里需要返回数值,表示循环次数。
  • while_script: 条件玄幻节点,脚本里需要返回true/false,表示什么条件才继续循环
  • break_script: 退出循环节点,脚本里需要返回true/false,表示什么时候退出循环

4.4 脚本的使用

脚本的使用和Java创建bean一样,直接通过节点ID就可以,如:

1
2
3
<chain name="chain2">
THEN(e);
</chain>

修改启动类,指定执行的Chain即可

1
flowExecutor.execute2Resp("chain2", null, null);