流程编排框架LiteFlow详解

1. 前言

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

2. 概述

liteFlow是一个轻量,快速的组件式流程引擎框架/规则引擎,组件编排,组件复用,帮助解耦业务代码,让每一个业务片段都是一个优雅的组件,并支持热加载规则配置,实现即时修改。

项目主页请点击:项目主页
项目文档请点击:项目文档
示例工程请点击:示例工程

3. 特点

复杂业务的解耦编排利器,为所有组件提供统一化的实现方式
基于规则文件来编排流程,支持xml,json,yml三种规则文件写法方式
框架中提供本地文件配置源,zk配置源的实现
框架提供自定义配置源,只需实现一个接口,即可从任何地方加载配置源
支持SpringBoot的自动装配,也支持Spring的配置和非Spring的项目
提供串行和并行2种模式,提供常见常见的表达式语句
可以定义脚本语言节点,支持QLExpress和Groovy两种脚本
组件可以支持重试,每个组件均可自定义重试配置和指定异常
提供无级嵌套的显式子流程模式,隐式子流程模式
数据槽隔离机制,在多并发下上下文独立而稳定
支持优雅平滑热刷新特性
对系统损耗极低,可以稳定运行在核心业务大规模的微服务中
自带简单的监控,能够知道每个组件的运行耗时排行

4. LiteFlow框架的作用

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

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

5. LiteFlow的设计原则

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

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

这个模式有几个好处:

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

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

6. LiteFlow不适用于场景

LiteFlow自开源来,经常有一些小伙伴来问我,如何做角色任务之间的流转,类似于审批流,A审批完应该是B审批,然后再流转到C角色。
这里申明下,LiteFlow只做基于逻辑的流转,而不做基于角色任务的流转。如果你想做基于角色任务的流转,推荐使用flowable,activiti这2个框架。

7. LiteFlow适用场景

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

8. LiteFlow相比于Flowable和Activiti

Flowable和Activiti都是极为优秀的流程引擎框架,其中Flowable的底层也是Activiti,他们除了能做基于任务角色的流程,也能做基于逻辑的流程,并且他们的基于BPM协议,很多基于BPM协议的编辑工具都能为他们可视化编辑流程。

LiteFlow和他们相比,虽然功能不如他们那么多,但是胜在轻量,高性能和极少学习成本上。而且这2款都是国外开源,集成起来比较重,而且文档本地化做的也不够好。LiteFlow拥有完善的本地化文档和使用范例。在大部分的场景可以帮助你改善你的系统。

9. 实现验证

案例基于liteflow-example实现
pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zrj</groupId>
    <artifactId>liteflow-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>liteflow-example</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <commons.lang3.version>3.4</commons.lang3.version>
        <commons-collections.version>4.1</commons-collections.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons.lang3.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>${commons-collections.version}</version>
        </dependency>

        <dependency>
            <groupId>com.yomahub</groupId>
            <artifactId>liteflow-spring-boot-starter</artifactId>
            <version>2.6.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.4</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

server.port=8580
liteflow.ruleSource=liteflow/*.xml
#-----------------以下非必须-----------------
##liteflow是否开启,默认为true
#liteflow.enable=true
##liteflow的banner是否开启,默认为true
#liteflow.print-banner=true
##zkNode的节点,只有使用zk作为配置源的时候才起作用
#liteflow.zk-node=/lite-flow/flow
##slot的数量,默认值为1024
#liteflow.slot-size=1024
##异步线程最长的等待时间秒(只用于when),默认值为15
#liteflow.when-max-wait-second=15
##异步线程池最大线程数,默认为16
#liteflow.when-max-workers=16
##异步线程池等待队列数,默认为512
#liteflow.when-queue-limit=512
##是否在启动的时候就解析规则,默认为true
#liteflow.parse-on-start=true
##全局重试次数,默认为0
#liteflow.retry-count=0
##是否支持不同类型的加载方式混用,默认为false
#liteflow.support-multiple-type=false
##是否开启监控log打印,默认值为false
#liteflow.monitor.enable-log=true
##监控队列存储大小,默认值为200
#liteflow.monitor.queue-limit=300
##监控一开始延迟多少执行,默认值为300000毫秒,也就是5分钟
#liteflow.monitor.delay=10000
##监控日志打印每过多少时间执行一次,默认值为300000毫秒,也就是5分钟
#liteflow.monitor.period=10000

activityFlow.xml

<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <chain name="activityFlow">
        <then value="activitySlotInitCmp"/>
        <then value="activityParamCheckCmp"/>
        <then value="activityToolsCheckCmp"/>
    </chain>
</flow>

ActivityReqVO

package com.zrj.liteflow.bean.activity;

import com.yomahub.liteflow.entity.data.AbsSlot;
import lombok.Builder;
import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * 活动请求对象
 *
 * @author zrj
 * @since 2021/11/15
 **/
@Data
@Builder
public class ActivityReqVO extends AbsSlot {
    
    
    /**
     * 活动编码
     */
    private String activityCode;

    /**
     * 活动名称
     */
    private String activityName;

    /**
     * 活动开始时间
     */
    private Date validStartTime;

    /**
     * 活动结束时间
     */
    private Date validEndTime;

    /**
     * 活动工具
     */
    private List<ActivityTools> activityToolsList;

}

ActivityTools

package com.zrj.liteflow.bean.activity;

import lombok.Builder;
import lombok.Data;

/**
 * @author zrj
 * @since 2021/11/15
 **/
@Data
@Builder
public class ActivityTools {
    
    
    /**
     * 活动编码
     */
    private String activityCode;
    /**
     * 活动工具编码
     */
    private String actToolCode;
    /**
     * 活动工具名称
     */
    private String actToolName;
}

ActivityParamCheckCmp

package com.zrj.liteflow.component.activity;

import com.yomahub.liteflow.core.NodeComponent;
import com.zrj.liteflow.slot.ActivitySlot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 活动请求参数校验
 *
 * @author zrj
 * @since 2021/11/15
 **/
@Slf4j
@Component("activityParamCheckCmp")
public class ActivityParamCheckCmp extends NodeComponent {
    
    
    @Override
    public void process() throws Exception {
    
    
        log.info("【活动请求参数校验】");
        ActivitySlot activitySlot = this.getSlot();
        if (!checkActParam(activitySlot.getActivityCode())) {
    
    
            activitySlot.setPrintLog("活动编码为空异常");
            throw new RuntimeException("活动编码为空异常");
        }
    }

    /**
     * 参数校验
     */
    private boolean checkActParam(String code) {
    
    
        if (code == null) {
    
    
            return false;
        }
        return true;
    }
}

ActivitySlotInitCmp

package com.zrj.liteflow.component.activity;

import com.yomahub.liteflow.core.NodeComponent;
import com.zrj.liteflow.bean.activity.ActivityReqVO;
import com.zrj.liteflow.slot.ActivitySlot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 初始化活动数据槽
 *
 * @author zrj
 * @since 2021/11/15
 **/
@Slf4j
@Component("activitySlotInitCmp")
public class ActivitySlotInitCmp extends NodeComponent {
    
    

    @Override
    public void process() throws Exception {
    
    
        log.info("【初始化活动数据槽】");
        ActivityReqVO req = this.getSlot().getRequestData();
        ActivitySlot activitySlot = this.getSlot();
        activitySlot.setActivityCode(req.getActivityCode());
        activitySlot.setActivityName(req.getActivityName());
        activitySlot.setActivityToolsList(req.getActivityToolsList());
    }

    /**
     * 是否进入该节点
     *
     * @return boolean
     */
    @Override
    public boolean isAccess() {
    
    
        ActivityReqVO req = this.getSlot().getRequestData();
        if (req != null) {
    
    
            return true;
        } else {
    
    
            return false;
        }
    }
}

ActivityToolsCheckCmp

package com.zrj.liteflow.component.activity;

import com.yomahub.liteflow.core.NodeComponent;
import com.zrj.liteflow.bean.activity.ActivityTools;
import com.zrj.liteflow.slot.ActivitySlot;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 活动请求参数校验
 *
 * @author zrj
 * @since 2021/11/15
 **/
@Slf4j
@Component("activityToolsCheckCmp")
public class ActivityToolsCheckCmp extends NodeComponent {
    
    
    @Override
    public void process() throws Exception {
    
    
        log.info("【活动工具校验】");
        ActivitySlot activitySlot = this.getSlot();
        List<ActivityTools> activityToolsList = activitySlot.getActivityToolsList();
        activitySlot.setPrintLog("活动流程测试成功");
    }
}

ActivitySlot

package com.zrj.liteflow.slot;

import com.yomahub.liteflow.entity.data.AbsSlot;
import com.zrj.liteflow.bean.activity.ActivityTools;
import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * 活动数据槽
 *
 * @author zrj
 * @since 2021/11/15
 **/
@Data
public class ActivitySlot extends AbsSlot {
    
    
    /**
     * 活动编码
     */
    private String activityCode;

    /**
     * 活动名称
     */
    private String activityName;

    /**
     * 活动开始时间
     */
    private Date validStartTime;

    /**
     * 活动结束时间
     */
    private Date validEndTime;

    /**
     * 活动工具
     */
    private List<ActivityTools> activityToolsList;

    /**
     * 步骤日志
     */
    private String printLog;
}

ActivityController

package com.zrj.liteflow.controller;

import cn.hutool.core.date.DateUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.entity.data.LiteflowResponse;
import com.zrj.liteflow.bean.activity.ActivityReqVO;
import com.zrj.liteflow.bean.activity.ActivityTools;
import com.zrj.liteflow.slot.ActivitySlot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zrj
 * @since 2021/11/15
 **/
@RestController
@RequestMapping("/activity")
public class ActivityController {
    
    

    @Resource
    private FlowExecutor flowExecutor;

    @GetMapping("/liteflow")
    public String liteflow() {
    
    
        ActivityReqVO activityReqVO = builder();
        //ActivityReqVO activityReqVO = null;

        try {
    
    
            LiteflowResponse<ActivitySlot> response = flowExecutor.execute2Resp("activityFlow", activityReqVO, ActivitySlot.class);
            return response.getSlot().getPrintLog();
        } catch (Throwable t) {
    
    
            t.printStackTrace();
            return "error";
        }
    }

    /**
     * 构建请求对象
     */
    private ActivityReqVO builder() {
    
    
        List<ActivityTools> activityToolsList = new ArrayList<>(10);
        activityToolsList.add(ActivityTools.builder().activityCode("20211115001").actToolName("大转盘").build());
        activityToolsList.add(ActivityTools.builder().activityCode("20211115001").actToolName("红包雨").build());

        return ActivityReqVO.builder()
                .activityCode("20211115001")
                .activityName("双十一大促")
                .validStartTime(DateUtil.date())
                .validEndTime(DateUtil.date())
                .activityToolsList(activityToolsList)
                .build();
    }

}

输出验证
http://localhost:8580/activity/liteflow

2021-11-15 19:05:26.017  INFO 6844 --- [nio-8580-exec-8] c.z.l.c.activity.ActivitySlotInitCmp     : 【初始化活动数据槽】
2021-11-15 19:05:26.017  INFO 6844 --- [nio-8580-exec-8] com.yomahub.liteflow.core.NodeComponent  : [33776573532900]:[O]start component[ActivityParamCheckCmp] execution
2021-11-15 19:05:26.017  INFO 6844 --- [nio-8580-exec-8] c.z.l.c.activity.ActivityParamCheckCmp   : 【活动请求参数校验】
2021-11-15 19:05:26.017  INFO 6844 --- [nio-8580-exec-8] com.yomahub.liteflow.core.NodeComponent  : [33776573532900]:[O]start component[ActivityToolsCheckCmp] execution
2021-11-15 19:05:26.017  INFO 6844 --- [nio-8580-exec-8] c.z.l.c.activity.ActivityToolsCheckCmp   : 【活动工具校验】
2021-11-15 19:05:26.017  INFO 6844 --- [nio-8580-exec-8] com.yomahub.liteflow.entity.data.Slot    : [33776573532900]:CHAIN_NAME[activityFlow]
activitySlotInitCmp==>activityParamCheckCmp==>activityToolsCheckCmp

猜你喜欢

转载自blog.csdn.net/m0_37583655/article/details/121341137