专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java微服务实现订单状态机(业务,规划,设计,实现)

temp10 2024-11-26 08:56:16 java教程 13 ℃ 0 评论

Java微服务实现订单状态机(业务,规划,设计,实现)

1 前言

Java微服务实现订单状态机(业务,规划,设计,实现)

电商平台所有模块中,订单系统作为比较核心的模块,它决定了整个流程能不能顺畅的执行,起着承上启下的作用(下单、支付、履约、售后、清结算、营销活动)。

订单系统的设计主要需要考虑订单字段、业务流程、状态机三大个方面,这些内容决定了订单系统稳定性与扩展性。

2 订单流程

订单流程指整个订单从产生到完成的整个流转过程,它包括正向流程和逆向的流程。

3 订单状态机

状态机表示了一笔订单的生命周期,按照一定的方向通过触发不同的事件产生数据流转的过程。

状态机v2.0

随着业务快速发展,我们的状态机也在逐步完善,在完善的过程中我们遇到两个问题

如何提供更多的订单状态来描述现实世界?

在提供更多的订单状态后,同时节省状态机维护成本?

为了解决这两个问题,我们采用大状态+小状态的设计。主、子订单上只维护大状态流转,抽象出物流、售后两个领域分别去维护各自的小状态。这样保证了主子订单上的状态不会特别多,同时又可以通过不同的领域中的小状态去满足业务需求。

主、子订单变更规则

正向状态是当全部子订单向前流转时,主订单才会向前走;

逆向状态是当子订单全部变成15(售后中) 主订单才变售后中. 子订单全部变成16(售后完成)主订单才变售后完成 子订单全部变成11(取消)主订单才变取消;


状态机在APP端的体现

团长C端提货界面

用户C端提货界面

思考与改进

1 目前物流领域的状态机只针对团长端开放,并没有针对C端用户开放。WMS和TMS作业都是按照团长+SKU纬度,团长收到货后承担这二次分拣的职责,由团长决定用户的货是否真的送达,所以WMS和TMS回传给订单的消息对用户是不准确的。

问题举例

小明在A团长下单5个苹果

小红在A团长下单5个苹果

23点仓库开始作业,发现一共只有5个苹果。

WMS TMS 下发出库、配送指令 A团长+5个苹果

OMS收到指令按照团长纬度更新订单状态,此时小明、小红的订单状态都被更新为司机送达

团长收到5个苹果后,选择把苹果全部给和他关系较好的小明。

此时小红没收到货、同时看到自己订单状态为司机送达就会很懵逼,这也是目前为什么履约状态只针对团长开放的原因。谁到货谁没到货团长线下决定的。

下面基于Java微服务来实现代码:

1. 有限状态机介绍

有限状态机,也称为 FSM (Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。有限状态机成立的必要条件有:

  1. 对象有一组互斥的状态(或对象的生命周期),且这组状态可以涵盖对象的创建到消亡的整个过程。
  2. 当向对象传入一个信号(或事件)时,对象的状态能从当前状态转换成另一种状态,或者保持状态不变。
  3. 状态是有限的。

如图例所示,红绿灯在同一时间只能亮一个颜色,控制程序可以定义 3 种不同的事件,每个事件定义好红绿灯的起始颜色和目标颜色,我们不需要直接去操作红绿灯开关,只需要按照一定的顺序发送事件过去,我们就可以精确控制红绿灯的工作,红绿灯的工作控制其实就是一个标准的 FSM。

由上所述,FSM 一般需要以下 4 个部分组成:

  1. 对象状态枚举。
  2. 对象事件枚举,并指定事件的起始状态和目标状态。
  3. 事件逻辑体,用于处理状态变更引起的业务逻辑。
  4. 事件注册工厂类,FSM 的唯一入口。

2. 从需求开始分析 FSM

当我们拿到一个类似需求时应该怎么入手呢?下面我们以一个简单的商城订单的 FSM 实现为例,从需求分析到代码实现为大家讲解:

首先我们应该从需求中提炼出一个订单具体需要经过哪些状态(这里我只列举了一个简单订单的正向状态)。

订单状态:待支付、待发货、待收货、已取消、已完成

列举出所有状态之后,在作图工具中把状态全部画出来,每个状态进行分析是否能转换为其他状态,如分析待支付状态,用户可以从待支付状态进行付款事件,待支付状态将会转换为待发货,用户也可以从待支付状态取消支付,待支付将会转换为订单取消状态。按照这个思路使用单向箭头将所有事件列举出来,并给每个事件起名字。

订单事件:下单事件、支付事件、发货事件、支付取消事件、收货事件

最后形成如下图所示的状态流转图

3. FSM 的 java 实现

按照上面所说,我们将 FSM 的 4 个部分声明出来:

3.1. 对象状态枚举类

使用枚举的方式穷举出订单所有可能的状态。

public enum OrderStatusEnum {
    Unpaid("Unpaid", "待支付"),
    UnShipping("UnShipping", "待发货"),
    UnReceiving("UnReceiving", "待收货"),

    Canceled("Canceled", "已取消"),
    Finished("Finished", "已完成");

    private final String code;
    private final String desc;

    OrderStatusEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

3.2. 对象事件枚举类

为了省事,我在这里把事件和状态做了关联,也可以单独拿出来做一个配置封装。

public enum OrderEventEnum {
    CreateOrder("CreateOrder", "下单事件", null, OrderStatusEnum.Unpaid),
    Payment("Payment", "支付事件", OrderStatusEnum.Unpaid, OrderStatusEnum.UnShipping),
    Shipping("Shipping", "发货事件", OrderStatusEnum.UnShipping, OrderStatusEnum.UnReceiving),
    Receiving("Receiving", "收货事件", OrderStatusEnum.UnReceiving, OrderStatusEnum.Finished),
    CancelPayment("CancelPayment", "支付取消事件", OrderStatusEnum.Unpaid, OrderStatusEnum.Canceled);

    private final String code;
    private final String desc;
    private final OrderStatusEnum sourceOrderStatus;
    private final OrderStatusEnum targetOrderStatus;

    OrderEventEnum(String code, String desc, OrderStatusEnum sourceOrderStatus, OrderStatusEnum targetOrderStatus) {
        this.code = code;
        this.desc = desc;
        this.sourceOrderStatus = sourceOrderStatus;
        this.targetOrderStatus = targetOrderStatus;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    public OrderStatusEnum getSourceOrderStatus() {
        return sourceOrderStatus;
    }

    public OrderStatusEnum getTargetOrderStatus() {
        return targetOrderStatus;
    }
}

3.3. 事件逻辑体

首先声明一个逻辑体基类,基类里面定义了事件过程数据缓存对象、事件条件校验方法、事件后处理方法、事件业务逻辑处理虚方法(需要由业务自己实现)。

@Slf4j
public abstract class BaseOrderFsmProcessor {
    private static final Map<Long, Object> FSM_DATA_MAP = new ConcurrentHashMap<>();

    /**
     * 执行业务逻辑
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     */
    public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
        log.info("OrderFSM_开始FSM事件:orderId={}", orderId);
        if (!checkRule(orderId, event)) {
            log.warn("OrderFSM_不满足条件,拒绝执行FSM事件:orderId={}", orderId);
            return true;
        }
        boolean b = process(orderId, event);
        log.info("OrderFSM_结束FSM事件:orderId={}", orderId);
        postHandler(orderId, event);
        return b;
    }

    /**
     * 业务逻辑实现类
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     * @throws Exception ex
     */
    public abstract boolean process(Long orderId, OrderEventEnum event) throws Exception;

    /**
     * 校验是否满足条件执行当亲process
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     */
    public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
        return true;
    }

    /**
     * 后置处理逻辑
     *
     * @param orderId 订单ID
     * @param event   事件类型
     */
    public void postHandler(Long orderId, OrderEventEnum event) throws Exception {

    }

    /**
     * 根据流程ID获取事件过程数据
     *
     * @param orderId 订单ID
     * @return 事件过程数据
     */
    public static Object getFsmData(Long orderId) {
        return FSM_DATA_MAP.remove(orderId);
    }

    /**
     * 根据流程ID设置事件过程数据
     *
     * @param orderId 订单ID
     * @param obj     事件过程数据
     */
    public static void setFsmData(Long orderId, Object obj) {
        FSM_DATA_MAP.put(orderId, obj);
    }

}

再跟进基类派生出不同事件对应的处理方法,一个事件要声明一个对应的方法:

@Slf4j
public class CreateOrderProcessor extends BaseOrderFsmProcessor {

    @Override
    public boolean process(Long orderId, OrderEventEnum event) throws Exception {
        log.info("业务逻辑执行中:className={},event={}", getClass().getSimpleName(), event.name());
        //TODO 模拟业务逻辑
        TimeUnit.MILLISECONDS.sleep(1000);
        setFsmData(orderId, Thread.currentThread().getName());

        log.info("业务逻辑执行完成");
        return true;
    }

    @Override
    public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
        log.info("执行条件检查通过");
        return true;
    }

    @Override
    public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
        log.info("执行后置处理逻辑");
        // TODO 将orderId的状态改为 event.getTargetOrderStatus()
    }
}

3.4. 事件注册工厂类

工厂类里封装了单例 FSM 执行的唯一入口和查询设置流程中间数据的入口。

@Slf4j
public class OrderFsmManager {

    private final Map<OrderEventEnum, BaseOrderFsmProcessor> orderProcessorMap = new HashMap<>();
    private volatile static OrderFsmManager orderFsmManager;

    private OrderFsmManager() {
        orderProcessorMap.put(OrderEventEnum.CreateOrder, new CreateOrderProcessor());
        orderProcessorMap.put(OrderEventEnum.Payment, new PaymentFsmProcessor());
    }

    /**
     * 获取fsm实例
     */
    public static OrderFsmManager getInstance() {
        if (orderFsmManager == null) {
            synchronized (OrderFsmManager.class) {
                if (orderFsmManager == null) {
                    orderFsmManager = new OrderFsmManager();
                }
            }
        }
        return orderFsmManager;
    }

    /**
     * 开始执行fsm事件
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     */
    public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
        if (!orderProcessorMap.containsKey(event)) {
            throw new Exception(String.format("MediaProcessFSM没有匹配到事件:orderId=%s,currentOrderEvent=%s"
                    , orderId, event));
        }
        return orderProcessorMap.get(event).fireProcess(orderId, event);
    }

    /**
     * 根据流程ID获取事件过程数据
     *
     * @param orderId 订单ID
     * @return 事件过程数据
     */
    public Object getFsmData(Long orderId) {
        return BaseOrderFsmProcessor.getFsmData(orderId);
    }

    /**
     * 根据流程ID设置事件过程数据
     *
     * @param orderId 订单ID
     * @param obj     事件过程数据
     */
    public void setFsmData(Long orderId, Object obj) {
        BaseOrderFsmProcessor.setFsmData(orderId, obj);
    }
}

3.5. 简单做个测试

	public static void main(String[] args) throws Exception {
        OrderFsmManager orderFsmManager = OrderFsmManager.getInstance();
//        boolean b1 = orderFsmManager.fireProcess(1L, OrderEventEnum.CreateOrder);
//        boolean b2 = orderFsmManager.fireProcess(2L, OrderEventEnum.Payment);
//        System.out.println(String.format("orderId=%s,data=%s",1, orderFsmManager.getFsmData(1L)));
//        System.out.println(String.format("orderId=%s,data=%s",2, orderFsmManager.getFsmData(2L)));

        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                String threadName = "thread-" + finalI;
                Thread.currentThread().setName(threadName);

                try {
                    if (finalI%2==0){
                        boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.CreateOrder);
                    }else {
                        boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.Payment);
                    }

                    System.out.println(String.format("threadName=%s,data=%s",threadName, orderFsmManager.getFsmData((long) finalI)));
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }).start();
        }


    }
11:17:39.213 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=2
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.213 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=1
11:17:39.213 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=0
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行条件检查通过
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行中:className=PaymentFsmProcessor,event=Payment
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行完成
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=1
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行后置处理逻辑
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=2
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-1,data=thread-1
threadName=thread-2,data=thread-2
11:17:40.246 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=0
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-0,data=thread-0

4. 结语

到这里 FSM 的封装就完成了,其中有一些设计可以根据自身技术选择进行调整,如工厂类的单例实现可以使用 spring 的 bean 注入方式,事件和状态的绑定关系可以外挂出来作为配置项等。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表