接单流程设计探索

背景

在物流系统中,接单是信息流的关键和重要的一环,每个业务场景都会对应一种标准接单流程,例如销售出、采购入等等。标准接单包括统一接口定义、统一数据模型、标准接单核心应用职责划分。而这个标准并不是在接口定义的初期就规划好的,通常会经历业务不断增长而带来的需求迭代、业务融合、组织架构调整或升级引起的流程优化与拆分。这样一些系列事件下来,可能一个接单应用会流转到多个部门,接单流程就会越来越丰富,可能包括多业务、多场景、个性化、各种开关、五花八门的扩展实现。

问题

在大接入背景下,我们聚焦在一个接单应用的一个接单方法上。或多或少在工作中都会遇到一下几种问题:

•瀑布式迭代,一个方法最终三五千行,难以阅读理解,牵一发动全身。

•大量个性化逻辑散落在下单得每个环节,梳理起来无从下手。

•方法串联时上下文样式各异,如果当初没有扩展性,后期改动变动大。

思路

针对这些问题,可以分为两个层面思考,战略和战术。

战略

这里的战略指的是模式,而接单场景可以利用工作台模式,工人(组件)按顺序围着工作台(上下文)生产两件(执行任务),资源(参数)从工作台拿取,这种模式可以做到组件解耦、稳定、可复用。保证业务流程灵活。

战术

围绕着工作台模式,可以提炼的一下几个关键点:

•组件定义

•上下文

•执行规则

组件定义

组件通常被定义为规则执行的最小单元,我们最常见的是普通组件,也就是调用就执行,这是大部分系统目前都在使用的组件。其实在流程规则中,组件就像编译语言,还应该具备布尔组件、条件组件、循环组件、并行组件、异常捕获组件等等。由于这些组件都可以包含在普通组件中,通过代码来实现条件、循环等逻辑。所以在执行流程定义时,无法清晰识别这些本应体现在流程中的逻辑。

组件定义通常还有一个值得关注的就是组件的数量,哪些逻辑可以归类到一个组件中,哪些需要分开。这里没有标准答案,有两个思路仅供参考:

1.如果订单已经归类不同子域,例如发货、收货、承运、产品、货品等,那就按照对应子域划分组件。这样更容易达成语言统一。

2.根据流程中写动作来定义组件,例如写库、下发wms、下配等。

上下文

在组件定义的时候都会定义上下文作为执行流程中出入参的载体。上下文得定义通常需要具备几个特点:

•传递性

•共享性

•动态性

每个组件都只关心上下文中与自己相关的内容,可以进行读取和更新,然后在流程中不断传递下去。并且在需求迭代过程中支持扩展上下文。

执行规则

执行规则就是约定各种组件按照何种规则执行。这里实现方式大多xml方式、Spring注入方式、显示组装方式成执行链,然后顺序执行。这种方案弊端就是无法体现条件判断、循环、并行。另一个问题就是大家深受SpringBoot思维的"毒害":约定大于配置,而逐渐放弃xml配置方式,让执行链组装藏在代码中。让执行规则更加不容易被发现,说白了就是执行规则没有与代码进行解耦。那么如果将执行规则单独抽象出来,就可以更进一步支持多种方式存储,例如数据库、redis、ducc等,这样热更就会成为可能。

答案

在不断实践和学习中,我发现了一个具备上述所有能力的开源组件LiteFlow

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

例子

要实现下面的流程:

流程规则:

scss 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <chain name="chain1">
        THEN(
            SWITCH(businessSwitch).TO(
//                中小件子流程
                THEN(smallChain).id("small"),
//                  冷链子流程
                THEN(coldChain).id("cold")
            ),
//        迭代执行
            ITERATOR(goodsIterator).DO(goodsItem),
//        选择器+默认
            SWITCH(kaSwitch).TO(dajiang, lining, nike).DEFAULT(defaultKa)
        );
    </chain>
    <chain name="smallChain">
//        并行
        WHEN(commonDept, smallWarehouse);
    </chain>
    <chain name="coldChain">
//        并行
        WHEN(commonDept, coldWarehouse);
    </chain>
</flow>

代码结构:

markdown 复制代码
.
├── LiteFlowDemoApplication.java
└── demos
    └── web
        ├── BasicController.java
        ├── context
        │   └── OrderContext.java
        ├── dto
        │   ├── Dept.java
        │   ├── Goods.java
        │   ├── Request.java
        │   └── WareHouse.java
        ├── enums
        │   ├── BusinessEnum.java
        │   └── KaEnum.java
        └── node
            ├── BusinessSwitchCmp.java
            ├── ColdWarehouseCmp.java
            ├── CommonDeptCmp.java
            ├── GoodsItemCmp.java
            ├── GoodsIteratorCmp.java
            ├── KaSwitchCmp.java
            ├── SmallWarehouseCmp.java
            └── ka
                ├── DaJiangCmp.java
                ├── DefaultCmp.java
                ├── LiNingCmp.java
                └── NikeCmp.java

8 directories, 21 files

业务类型判断:

scala 复制代码
@LiteflowComponent("businessSwitch")
public class BusinessSwitchCmp extends NodeSwitchComponent {
    @Override
    public String processSwitch() throws Exception {
        Request request = this.getRequestData();
        if(Objects.equals(request.getDept().getDeptNo(), "dept1")) {
            return BusinessEnum.SMALL.getBusiness();
        } else {
            return BusinessEnum.COLD.getBusiness();
        }
    }
}

迭代器组件:

scala 复制代码
@LiteflowComponent("goodsIterator")
public class GoodsIteratorCmp extends NodeIteratorComponent {
    @Override
    public Iterator<Goods> processIterator() throws Exception {
        Request requestData = this.getRequestData();
        return requestData.getGoodList().iterator();
    }
}

循环执行:

scala 复制代码
@Slf4j
@LiteflowComponent("goodsItem")
public class GoodsItemCmp extends NodeComponent {
    @Override
    public void process() throws Exception {
        log.info("goods item index = {}", this.getLoopIndex());
        //获取当前循环对象
        Goods goods = this.getCurrLoopObj();
        //赋值为当前循环索引
        goods.setGoodsId(this.getLoopIndex());
        OrderContext orderContext = this.getContextBean(OrderContext.class);
        List<Goods> goodsList = orderContext.getData("goods");
        if(goodsList == null) {
            goodsList = new ArrayList<>();
            this.getContextBean(OrderContext.class).setData("goods", goodsList);
        }
        goodsList.add(goods);
    }
}

测试用例

ini 复制代码
public String testConfig() {
        Request request = new Request();
        Dept dept = new Dept();
        dept.setDeptNo("nike");
        request.setDept(dept);
        WareHouse wareHouse = new WareHouse();
        request.setWareHouse(wareHouse);
        Goods goods1 = new Goods();
        goods1.setGoodsName("goods1");
        Goods goods2 = new Goods();
        goods2.setGoodsName("goods2");
        request.setGoodList(Arrays.asList(goods1, goods2));
        //参数1为流程标识,参数2为初始入参,参数3为上下文类型约定
        LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain1",request, OrderContext.class);
        //结果中获取上下文
        OrderContext contextBean = liteflowResponse.getContextBean(OrderContext.class);
        List<Goods> goodsList = contextBean.getData("goods");
        WareHouse warehouse = contextBean.getData("warehouse");
        Dept dept1 = contextBean.getData("dept");
        log.info("=== dept = {}", JsonUtil.toJsonString(dept1));
        log.info("=== warehouse = {}", JsonUtil.toJsonString(warehouse));
        log.info("=== goodsList = {}", JsonUtil.toJsonString(goodsList));
        return "yes";
    }

特点

个人觉得LiteFlow的特点包括一下几点:

组件定义统一: 所有的逻辑都是组件,为所有的逻辑提供统一化的组件实现方式

规则持久化: 框架原生支持把规则存储在标准结构化数据库,Nacos,Etcd,Zookeeper,Apollo,Redis、自定义扩展。

上下文隔离机制: 可靠的上下文隔离机制,无需担心高并发情况下的数据串流

支持广泛: Springboot,Spring还是任何其他java框架都支持。

规则轻量: 基于规则文件来编排流程,学习规则门槛低

总结

LiteFlow是强大的流程规则框架,之所以没有直接把LiteFlow放在标题中,是跟大家一起透过问题看本质,最终找到合适的解决方案,而LiteFlow通过设计和抽象能力解决问题,更加值得借鉴和学习。

相关推荐
不要秃头啊4 小时前
别再谈提效了:AI 时代的开发范式本质变了
前端·后端·程序员
jonjia5 小时前
引入新维度化解权衡难题
程序员
jonjia5 小时前
优秀的工程师如何打破规则
程序员
jonjia5 小时前
在大厂交付大型项目的策略
程序员
jonjia5 小时前
RFC 与设计文档
程序员
jonjia5 小时前
为什么你(或任何人)应该成为一名研发经理?
程序员
jonjia5 小时前
管理技术质量 (Manage Technical Quality)
程序员
jonjia5 小时前
大厂软件工程师职业发展路径
程序员
jonjia5 小时前
关于工程师与影响力
程序员
jonjia5 小时前
多层上下文 (Layers of Context)
程序员