1. 前言
在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护成本就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现
如何打破僵局?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
2
3
4
5<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.12.2.3</version>
</dependency>组件定义
组件a
1
2
3
4
5
6
7
8
9
10
11
12
13import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
/**
* @author xiaoyuge
*/
public class AComponent extends NodeComponent {
public void process() {
System.out.println("-----a-------");
}
}组件b
1
2
3
4
5
6
7
8
9
10
11
12
13import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
/**
* @author xiaoyuge
*/
public class BComponent extends NodeComponent {
public void process() {
System.out.println("-----b-------");
}
}组件c
1
2
3
4
5
6
7
8
9
10
11
12
13import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
/**
* @author xiaoyuge
*/
public class CComponent extends NodeComponent {
public void process() {
System.out.println("-----c-------");
}
}application.yml 配置文件
1
2liteflow:
rule-source: classpath:flow.xml也可以自定配置类使用注解
@LiteFLowConfig
来指定配置文件1
2
3
4
public class YourLiteflowConfig {
// ...
}规则文件定义
1
2
3
4
5
6
<flow>
<chain name="chain1">
THEN(a, b, c);
</chain>
</flow>启动类
1
2
3
4
5
6
7
public class LiteFlowDemoApplication {
public static void main(String[] args) {
SpringApplication.run(LiteFlowDemoApplication.class, args);
}
}测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package 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;
public class LiteFlowController {
private FlowExecutor flowExecutor;
public void index() throws ExecutionException, InterruptedException {
final Future<LiteflowResponse> liteflowResponseFuture = flowExecutor.execute2Future("chain1", "arg");;
System.out.println(liteflowResponseFuture.get());
}
}启动并访问测试类,输出日志如下
1
2
3
4
5-----a-------
-----b-------
-----c-------
a<0>==>b<0>==>c<0>
com.yomahub.liteflow.flow.LiteflowResponse@1fce8e14
3.2 进阶版
编写两个组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MessageNode extends NodeComponent {
public void process() {
Slot slot = this.getSlot();
System.out.println("获取参数"+slot.getRequestData().toString());
System.out.println("-----发送消息逻辑------");
}
}
public class PointNode extends NodeComponent {
public void process() {
//获取参数
Slot slot = this.getSlot();
System.out.println("获取参数"+slot.getRequestData().toString());
System.out.println("-----发放积分逻辑------");
}
}编写规则文件
1
2
3
4
5
6
7
<flow>
<chain name="flow">
<!--在同一个并行组中, then 和 when 来表示同步节点和异步节点。 -->
THEN(WHEN(messageNode, pointNode));
</chain>
</flow>流程触发
1
2
3
4
5
public void index() {
//完成订单的其他逻辑,传递参数
flowExecutor.execute2Resp("flow", "这是order的参数");
}
4. 扩展
4.1 LiteFlow脚本组件
LiteFlow框架目前一共支持6种脚本语言:Groovy
、Javascript
、QLExpress
、Python
、Lua
、Aviator
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 |
|
这里定义了一个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 | <chain name="chain2"> |
修改启动类,指定执行的Chain即可
1 | flowExecutor.execute2Resp("chain2", null, null); |