聊一聊组件化可编排编程

文章目录

    • 前言
    • 1、LiteFlow框架的优势
    • 2、LiteFlow的设计原则
    • 3、LiteFlow不适用于哪些场景
    • 4、LiteFlow适用于哪些场景
    • 5、LiteFlow特性
    • 6、SpringBoot场景应用
      • 6.1 依赖
      • 6.2 组件的定义
      • 6.3 SpringBoot配置文件
      • 6.4 定义规则文件
      • 6.5 编写SpringBoot启动代码
      • 6.6 执行第一个编排程序
      • 6.7 调整编排顺序
    • 7、组件讲解
      • 7.1 组件方法
      • 7.2 选择组件
      • 7.3 条件组件
    • 8、EL规则
      • 8.1 串行编排
      • 8.2 并行编排
      • 8.3 选择编排
      • 8.4 条件编排
      • 8.5 使用子流程
      • 8.6 使用子变量
    • 9、数据上下文
    • 10、总结

今天给大家介绍一款轻量、快速、稳定可编排的组件式规则引擎框架LiteFlow。

前言

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

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

1、LiteFlow框架的优势

如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个轻量,快速的组件式规则引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件,并支持热加载规则配置,实现即时修改。

使用LiteFlow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。这样,所有的组件,都能按照你的规则配置去进行复杂的流转。

2、LiteFlow的设计原则

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

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

这个模式有几个好处:

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

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

3、LiteFlow不适用于哪些场景

LiteFlow只做基于逻辑的流转,而不做基于角色任务的流转。如果你想做基于角色任务的流转,推荐使用flowable (opens new window),activiti (opens new window)这2个框架。

4、LiteFlow适用于哪些场景

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

5、LiteFlow特性

  • 组件定义统一: 所有的逻辑都是组件,为所有的逻辑提供统一化的组件实现方式,小身材,大能量。
  • 规则轻量: 基于规则文件来编排流程,学习规则表达式入门只需要5分钟,一看既懂。
  • 规则多样化: 规则支持xml、json、yml三种规则文件写法方式,喜欢哪种用哪个。
  • 任意编排: 同步异步混编,再复杂的逻辑过程,利用LiteFlow的规则,都是易如反掌,看规则文件就能知道逻辑是如何运转的,所见即所得。
  • 规则能从任意地方加载: 框架中提供本地文件配置源和zk配置源的实现,也提供了扩展接口,您可以把规则存储在任何地方。
  • 优雅热刷新机制: 规则变化,无需重启您的应用,即时改变应用的规则。高并发下不会因为刷新规则导致正在执行的规则有任何错乱。
  • 支持广泛: 不管你的项目是不是基于Springboot,Spring还是任何其他java框架构建,LiteFlow都能游刃有余。
  • JDK支持: 从JDK8到JDK17,统统支持。无需担心JDK版本。
  • 脚本语言支持: 可以定义脚本语言节点,支持QLExpress和Groovy两种脚本。未来还会支持更多的脚本语言。
  • 规则嵌套支持: 只要你想的出,你可以利用简单的表达式完成多重嵌套的复杂逻辑编排。
  • 组件重试支持: 组件可以支持重试,每个组件均可自定义重试配置和指定异常。
  • 上下文隔离机制: 可靠的上下文隔离机制,你无需担心高并发情况下的数据串流。
  • 声明式组件支持: 你可以让你的任意类秒变组件。
  • 详细的步骤信息: 你的链路如何执行的,每个组件耗时多少,报了什么错,一目了然。
  • 稳定可靠: 历时2年多的迭代,在各大公司的核心系统上稳定运行。
  • 性能卓越: 框架本身几乎不消耗额外性能,性能取决你的组件执行效率。
  • 自带简单监控: 框架内自带一个命令行的监控,能够知道每个组件的运行耗时排行。

6、SpringBoot场景应用

6.1 依赖

LiteFlow提供了liteflow-spring-boot-starter依赖包,提供自动装配功能。具体依赖内容如下:

<dependency>
	<groupId>com.yomahubgroupId>
	<artifactId>liteflow-spring-boot-starterartifactId>
	<version>2.8.5version>
dependency>

6.2 组件的定义

实现第一个组件

@Component("a")
//@LiteflowComponent(id = "a", name = "A组件") 也可使用该注解,给组件起别名,方便日志查看
public class ACmp extends NodeComponent {

	private static final Logger log = LoggerFactory.getLogger(ACmp.class);

	@Override
	public void process() {
		
		log.info("执行了A组件。。。");
	}
}

以此类推再分别定义b,c组件,Bcmp和CCmp。在这里,不再详细列出。

6.3 SpringBoot配置文件

然后,在SpringBoot的application.properties里添加如下配置(application.yml内容类似):

liteflow.rule-source=config/flow.el.xml

6.4 定义规则文件

同时,你得在resources下的config/flow.el.xml中定义规则:



    
        THEN(a, b, c);
    

6.5 编写SpringBoot启动代码

@SpringBootApplication
public class MessageDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MessageDemoApplication.class, args);
    }

}

编写SpringBoot启动时运行代码:

@Component
public class ChainExecute implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(ChainExecute.class);

    @Resource
    private FlowExecutor flowExecutor;
    
    @Override
    public void run(String... args) throws Exception {
        LiteflowResponse response = flowExecutor.execute2Resp("chain1", "agrs");
    }
}

6.6 执行第一个编排程序

运行MessageDemoApplication打印出下面日志(为了方便查看,日志经过整理):

INFO ACmp: 执行了A组件。。。
INFO BCmp: 执行了B组件。。。
INFO CCmp: 执行了C组件。。。

6.7 调整编排顺序

将编排顺序调整从THEN(a, b, c)调整为THEN(a, c, b)运行,查看日志内容如下:

INFO ACmp: 执行了A组件。。。
INFO CCmp: 执行了C组件。。。
INFO BCmp: 执行了B组件。。。

7、组件讲解

在LiteFlow里,目前包含普通组件、选择组件 和 条件组件。
普通组件需要继承 NodeComponent类、选择组件需要继承 NodeSwitchComponent、条件组件需要继承 NodeIfComponent。在第6节里,我们主要讲解了普通组件,这里不再做示例讲解。

7.1 组件方法

可覆盖方法

  • isAccess

推荐实现isAccess方法,表示是否进入该节点,可以用于业务参数的预先判断

  • isContinueOnError

表示出错是否继续往下执行下一个组件,默认为false

  • isEnd

如果覆盖后,返回true,则表示在这个组件执行完之后立马终止整个流程。对于这种方式,由于是用户主动结束的流程,属于正常结束,所以最终的isSuccess是为true的。

  • beforeProcess和afterProcess

流程的前置和后置处理器,其中前置处理器,在isAccess 之后执行。

  • onSuccess和onError

流程的成功失败事件回调

this关键字可以调用的方法

在组件节点里,随时可以通过方法this.getContextBean(clazz)获取当前你自己定义的上下文,从而可以获取任何数据。

  • getNodeId
    获取组件ID。

  • getName
    获取组件别名。

  • getChainName
    获取当前执行的流程名称。

  • getRequestData
    获取流程的初始参数。

  • setIsEnd
    表示是否立即结束整个流程 ,用法为this.setIsEnd(true)。对于这种方式,由于是用户主动结束的流程,属于正常结束,所以最终的isSuccess是为true的。

  • getTag
    获取这个组件的标签信息。

  • invoke和invoke2Response
    调用隐式流程。

7.2 选择组件

在实际业务中,往往要通过动态的业务逻辑判断到底接下去该执行哪一个节点,这就引申出了选择节点,选择节点可以用于SWITCH关键字中。
将组件A改造成以下代码:

@Component("a")
//@LiteflowComponent(id = "a", name = "A组件")
public class ACmp extends NodeSwitchComponent {

	private static final Logger log = LoggerFactory.getLogger(ACmp.class);

	@Override
	public String processSwitch() throws Exception {
		log.info("执行了选择A组件。。。");
		String para = this.getRequestData();
		if (para != null) {
			return "c";
		} else {
			return "b";
		}
	}

}

表达式调整为:


    SWITCH(a).to(b, c);

重新执行后,运行结果如下:

INFO ACmp: 执行了选择A组件。。。
INFO CCmp: 执行了C组件。。。

7.3 条件组件

条件组件,也可以称之为IF组件,返回是一个true/false。可用于IF...ELIF...ELSE等关键字。
添加XCmp组件,代码如下:

@Component("x")
public class XCmp extends NodeIfComponent {
	private static final Logger log = LoggerFactory.getLogger(XCmp.class);

	@Override
	public boolean processIf() throws Exception {
		log.info("执行了条件X组件。。。");
		return true;
	}
}

表达式调整为:


    IF(x, a, b);

执行程序,运行结果如下:

INFO XCmp:执行了条件X组件。。。
INFO ACmp: 执行了选择A组件。。。

我们发现,在执行选择A组件后,并没出现7.2的执行结果,可以调整EL表达式,达到预期结果。


    IF(x, SWITCH(a).to(b, c), b);

再次执行:

INFO XCmp:执行了条件X组件。。。
INFO ACmp: 执行了选择A组件。。。
INFO CCmp: 执行了C组件。。。

8、EL规则

在上面的示例中,我们体验到规则表达式的强大。一切复杂的流程在LiteFlow表达式的加持下,都异常丝滑简便。我们只需要很短的时间即可学会如何写一个很复杂流程的表达式。

8.1 串行编排

如果你要依次执行a,b,c,d四个组件,你可以用THEN关键字,需要注意的是,THEN必须大写。


    THEN(a, b, c, d);

图示为:

a
b
c
d

8.2 并行编排

使用用WHEN将a,b,c并行编排。


    WHEN(a, b, c);

然后,我们使用 THENWHEN 进行混合编排。


    THEN(a, WHEN(b, c, d), e);

图示为:

a
b
c
d
e

在以上示例里,b,c,d默认并行都执行完毕后,才会执行e。

8.3 选择编排

我们在写业务逻辑的时候,通常会碰到选择性问题,即,如果返回结果1,则进入A流程,如果返回结果2,则进入B流程,如果返回结果3,则进入C流程。这时,我们可以用SWITCHto的组合关键字,注意的是SWITCH必须大写,to小写。


    SWITCH(a).to(b, c, d);

图示为:

a
b
c
d

8.4 条件编排

条件编排是选择编排一个变种,选择编排是根据逻辑去选择多个子项中的一项。而条件编排只有真和假2个子项,这处理某些业务的过程中非常有用。简单理解,条件编排就是变成语言中的if else。


    THEN(
        IF(x, a),
        b
    );


图示为:

true
false
x
a
b

我们还可以通过IF编排一个三元运算:


    THEN(
        IF(x, a, b),
        c
    );


图示为:

true
false
x
a
b
c

使用ELSE表达式


    IF(x1, a).ELIF(x2, b).ELIF(x3, c);


图示为:

true
false
true
false
true
x1
a
x2
b
x3
c

8.5 使用子流程

接下来,我们再看一下复杂的流程

a
b
c
d
h
j
k
i
X
m
n
z

上面的图可以写成以下规则表达式:


    THEN(
        a, b,
        WHEN(
            THEN(c, WHEN(j, k)),
            d,
            THEN(h, i)
        ),
        SWITCH(X).to(
            m,
            n
        ),
        z
    );


以上编排,多少会让人感觉有点复杂,还容易出现问题。我们可以通过子流程,进行重新编排:


    THEN(
    	a, b,
    	WHEN(chain1, d, chain2),
    	chain3,
    	z
    );



  	THEN(c, WHEN(j, k));



  	THEN(h, i);



  	SWITCH(X).to(m, n),

8.6 使用子变量

针对8.5的流程,我们还可以通过子变量进行重新编排:


    t1 = THEN(c, WHEN(j, k));
    t2 = THEN(h, i);
    t3 = SWITCH(X).to(m, n);
    
    THEN(
        a, b,
        WHEN(t1, d, t2),
        t3,
        z
    );


9、数据上下文

平时我们写程序时,A调用B,那A一定要把B所需要的参数传递给B,要做到可编排,一定是消除每个组件差异性的。如果每个组件出参入参都不一致,那就没法编排了。
在LiteFlow框架体系中,每个组件的定义中是不需要接受参数的,也无任何返回的。每个组件只需要从数据上下文中获取自己关心的数据即可,而不用关心此数据是由谁提供的,同样的,每个组件也只要把自己执行所产生的结果数据放到数据上下文中即可,也不用关心此数据到底是提供给谁用的。这样一来,就从数据层面一定程度的解耦了。从而达到可编排的目的。

接下来,我们看一下LiteFlow的源码,了解下数据上下文处理方式:

public LiteflowResponse execute2Resp(String chainId, Object param) {
	return this.execute2Resp(chainId, param, DefaultContext.class);
}

public LiteflowResponse execute2Resp(String chainId, Object param, Class... contextBeanClazzArray) {
	return this.execute2Resp(chainId, param, contextBeanClazzArray, null, null, InnerChainTypeEnum.NONE);
}

public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) {
	return this.execute2Resp(chainId, param, null, contextBeanArray, null, InnerChainTypeEnum.NONE);
}

所以,在整个流程中,我们可以这么传:

flowExecutor.execute2Resp("chain1", 流程初始参数);

也可以这么传:

flowExecutor.execute2Resp("chain1", 流程初始参数, CustomContext.class);

也可以这么传:

flowExecutor.execute2Resp("chain1", 流程初始参数, OrderContext.class, UserContext.class, SignContext.class);

还是不开心的话,那就这么传:

OrderContext orderContext = new OrderContext();
orderContext.setOrderNo("abcdefg");
flowExecutor.execute2Resp("chain1", null, orderContext);

10、总结

LiteFlow还有很多高级特性,比如隐式流程啊,事件回调啊,声明式组件,组件切面啊,步骤信息,线程池的自定义,私有投递,还有简单监控。这款国产规则引擎快,在逻辑解偶这块,是要玩出花的节奏!
目前LiteFlow流程编排支持XML、Json和Yaml文件,也提供了zk对规则文件的解析处理。如果想用数据进行规则编排的,可以通过代码动态编排。从官方的性能测试来看,整体表现也不俗。

你可能感兴趣的:(规则引擎,java)