开发者社区 > 博文 > 接单流程设计探索
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

接单流程设计探索

  • jd****
  • 2024-10-30
  • IP归属:北京
  • 100浏览

    背景

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


    问题

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

    • 瀑布式迭代,一个方法最终三五千行,难以阅读理解,牵一发动全身。
    • 大量个性化逻辑散落在下单得每个环节,梳理起来无从下手。
    • 方法串联时上下文样式各异,如果当初没有扩展性,后期改动变动大。


    思路

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

    战略

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

    战术

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

    • 组件定义
    • 上下文
    • 执行规则


    组件定义

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


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

    1. 如果订单已经归类不同子域,例如发货、收货、承运、产品、货品等,那就按照对应子域划分组件。这样更容易达成语言统一。
    2. 根据流程中写动作来定义组件,例如写库、下发wms、下配等。

    上下文

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

    • 传递性
    • 共享性
    • 动态性

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


    执行规则

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


    答案

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

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


    例子

    要实现下面的流程:



    流程规则:

    <?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>
    

    代码结构:

    .
    ├── 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
    
    

    业务类型判断:

    @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();
            }
        }
    }
    

    迭代器组件:

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

    循环执行:

    @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);
        }
    }
    

    测试用例

    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通过设计和抽象能力解决问题,更加值得借鉴和学习。


    文章数
    2
    阅读量
    103

    作者其他文章

    01 百川仓配切量接口成长史