--------写在前面, 利用空余时间学习轻量级规则引擎框架

为什么要写这篇文章? 规则引擎在实际的业务中非常的重要。 在特定的业务场景中, 比如, 在医院做一个胃镜的检查, 流程挂号就不说了, 就说检查的流程, 服务端收到这个病人的信息后, 需要根据病人的信息, 进行一个流程式的筛选, 比如, 在做检查之前, 需要空腹, 是否服用造影剂等等, 可能这一系列的规则有非常多, 业务代码写下来会变得非常多, 非常的臃肿且耦合度很高, 后续不容易维护, 这个框架可以解决这一系列痛点。框架可以支持到java8 , 可以说是非常的友好

本次的所有演示项目基于 java 17, springBoot 2.7.1, liteflow-2.13.0

业务说明:

一个短信业务接了多个第三方供应商,某些业务需要查询第三方供应商剩余的短信包数量去选择剩余量最多的渠道去批量发送

引入依赖

        <dependency>
            <groupId>com.yomahub</groupId>
            <artifactId>liteflow-spring-boot-starter</artifactId>
            <version>2.13.0</version>
        </dependency>

具体的文档可以去看官方的文档, 这里仅做讲解 https://liteflow.cc/

先来看看这样一个流程XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC  "liteflow" "https://liteflow.cc/liteflow.dtd">
<flow>
    <chain name="channelSenderChain">
        selectBestChannel = THEN(
                                WHEN(
                                    channel1Query, channel2Query, channel3Query,
                                    channel4Query, channel5Query, channel6Query
                                ),
                                channelSelector
                            ).id("branch1");

        selectBizChannel = THEN(
                                biz1,
                                SWITCH(if_2).to(
                                    channel3,
                                    channel4,
                                    SWITCH(if_3).to(channel5, channel6).id("s3")
                                ).id("s2")
                            ).id("branch2");

        THEN(
            packageData,
            SWITCH(if_1).to(
                channel1,
                channel2,
                selectBestChannel,
                selectBizChannel
            ),
            batchSender
        );
    </chain>
</flow>

流程上下文

public class BatchMessageResultContext {

    private List<QueryVO> queryResultList;

    private String finalResultChannel;

    public List<QueryVO> getQueryResultList() {
        return queryResultList;
    }

    public void setQueryResultList(List<QueryVO> queryResultList) {
        this.queryResultList = queryResultList;
    }

    public String getFinalResultChannel() {
        return finalResultChannel;
    }

    public void setFinalResultChannel(String finalResultChannel) {
        this.finalResultChannel = finalResultChannel;
    }

    public void addQueryVO(QueryVO queryVO){
        if (queryResultList == null){
            queryResultList = new ArrayList();
        }
        queryResultList.add(queryVO);
    }
}

WHEN 代表并行, THEN 代表串行, SWITCH 代表流程开关, 这里可以选择走哪个流程, 下面是总的流程图

我们来看看几个关键节点, 看看官网的示例是怎么写的

首先是流程的核心选择节点 if_1, 它重写选择组件的 processSwitch() 的方法, 返回值是 branch1, 也就是 selectBestChannel ,

@LiteflowComponent(id = "if_1", name = "业务判断1")
public class IF1SwitchCmp extends NodeSwitchComponent {
    @Override
    public String processSwitch() throws Exception {
        //模拟业务耗时
        int time = new Random().nextInt(1000);
        Thread.sleep(time);

        //这里写死跳到并行获取剩余量那条分支,你可以改成其他分支测试
        return "branch1";
    }
}

进入到这里后, 会执行到一个串行和并行的组件, 也就是先执行WHEN的组件, 这里会一起执行 channel1Query ~channel6Query

        selectBestChannel = THEN(
                                WHEN(
                                    channel1Query, channel2Query, channel3Query,
                                    channel4Query, channel5Query, channel6Query
                                ),
                                channelSelector
                            ).id("branch1");

我们具体来看看这里的代码, 举例一个, 每一个ChannelNQueryCmp 都会模拟一个剩余量,

@LiteflowComponent(id = "channel1Query", name = "获取渠道1剩余量")
public class Channel1QueryCmp extends NodeComponent {
    @Override
    public void process() throws Exception {
        //模拟查询业务耗时
        int time = new Random().nextInt(1000);
        Thread.sleep(time);

        //mock下渠道1有2w条剩余量
        BatchMessageResultContext context = this.getContextBean(BatchMessageResultContext.class);
        context.addQueryVO(new QueryVO("channel1", 20000));
    }
}

@LiteflowComponent(id = "channelSelector", name = "渠道余量最大选择器")
public class ChannelSelectorCmp extends NodeComponent {
    @Override
    public void process() throws Exception {

        BatchMessageResultContext context = this.getContextBean(BatchMessageResultContext.class);

        List<QueryVO> queryList = context.getQueryResultList();

        //选择渠道余量最大的
        QueryVO vo = queryList.stream().min((o1, o2) -> o2.getAvailCount() - o1.getAvailCount()).orElse(null);

        assert vo != null;
        context.setFinalResultChannel(vo.getChannel());
    }
}

public class QueryVO {

    //渠道名称
    private String channel;

    //剩余短信包数量
    private int availCount;

    public QueryVO(String channel, int availCount) {
        this.channel = channel;
        this.availCount = availCount;
    }

    public String getChannel() {
        return channel;
    }

    public void setChannel(String channel) {
        this.channel = channel;
    }

    public int getAvailCount() {
        return availCount;
    }

    public void setAvailCount(int availCount) {
        this.availCount = availCount;
    }
}

这里是从 上下文的对象里面获取到 每一个通道中放入到上下文中的数据, 然后通过筛选ChannelSelectorCmp出剩余量最多的

最后是通过执行器,得到最终的流程返回值

@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 {
        //第二个参数为流程入参,示例中没用到,所以传null,实际业务是有值的
        LiteflowResponse response = flowExecutor.execute2Resp("channelSenderChain", null, BatchMessageResultContext.class);
        BatchMessageResultContext context = response.getContextBean(BatchMessageResultContext.class);
        if (response.isSuccess()){
            log.info("执行成功,最终选择的渠道是{}", context.getFinalResultChannel());
        }else{
            log.error("执行失败", response.getCause());
        }
    }
}

这里是实现了springboot 的一个接口 CommandLineRunner , 这个类主要是在应用启动后 执行的一个类, 通常用于预加载部分热查询的业务数据

这里就是大致的流程