• 探索状态驱动开发的奇妙世界——Cola-StateMachine的介绍与使用


    1. 前言

    前面接受了Spring实现的状态机Spring StateMachine,这个状态机的优点在于功能很完备,缺点也是功能十分完备。

    完备到什么程度了,提供了状态机的高级玩法,比如状态的嵌套、状态的并行、子状态机等等。但是在开发中我们并不需要这些。

    除此之外,就是性能差的问题,包括但不仅限于Spring StateMachine在内的所有开源状态机都是有状态的,也就意味着状态机记住先前的状态和输入,以便在进行状态转换时使用这些信息作出决策。这就导致了这些有状态的状态机出现了线程安全的问题。

    而我们的系统往往是分布式多线程的,所以为了解决线程安全问题,我们不得不每次都要build一个实例。这就会导致了性能有所下降。

    image-20230915233723638

    倘若状态机构造十分复杂,并且系统QPS要求又很高的的情况下,肯定出现严重的性能问题。


    2. Cola-StateMachine概述

    Cola-StateMachine组件是一种轻量级的、无状态的、基于注解的状态机实现。可以方便管理订单的状态转换。

    COLA框架的状态机使用了连贯接口(Fluent Interfaces)来定义状态和事件,以及对应的动作和检查。

    COLA框架的状态机是COLA 4.0应用架构的一部分,旨在控制复杂度,提高开发效率。开发背景可见实现一个状态机引擎,教你看清DSL的本质。

    GITHUB地址:alibaba/COLA: 🥤 COLA: Clean Object-oriented & Layered Architecture (github.com)

    Cola-StateMachine的核心概念如下:

    1. State:状态
    2. Event:事件,状态由事件触发,引起变化
    3. Transition:流转,表示从一个状态到另一个状态
    4. External Transition:外部流转,两个不同状态之间的流转
    5. Internal Transition:内部流转,同一个状态之间的流转
    6. Condition:条件,表示是否允许到达某个状态
    7. Action:动作,到达某个状态之后,可以做什么
    8. StateMachine:状态机

    image-20230915235042292

    Cola-StateMachine和其他开源状态机框架最大的一个亮点,就在于解决了性能问题,将状态机变成无状态的。

    Cola-StateMachine为什么是无状态的?

    首先,我们得知道,为什么其他开源状态机是有状态的,那是因为状态机内部维护了两个状态:初始状态与当前状态。因为这些状态机需要依靠这两个状态来做出决策。

    但是Cola-StateMachine将这两个状态移除了,也就导致无法获取状态机的初始状态与当前状态。但是实际上我们也并不需要知道,只需要接收目标的状态,然后检查条件,解决执行事件即可,然后返回目标状态,然后根据目标状态更新Order状态即可。

    这其实就是一个状态流转的DSL表达式,全过程都是无状态的。

    image-20230916000108400


    3. Cola-StateMachine相关API

    StateMachineBuilder

    StateMachineBuilder方法说明
    ExternalTransitionBuilder externalTransition()用于一个流转的构建器
    ExternalTransitionsBuilder externalTransitions()用于多个流转的构建器
    InternalTransitionBuilder internalTransition()开始构建内部流转
    StateMachine build(String machineId)对状态机开始构建,并在StateMachineFactory中注册

    StateMachine

    StateMachine方法说明
    boolean **verify**(S sourceStateId, E event)验证一个事件E是否可以从当前状态S触发
    S **fireEvent**(S sourceState, E event, C ctx)向状态机发送一个事件E,触发状态机,并返回目标状态
    String **getMachineId**()获取状态机的标识符MachineId
    void **showStateMachine**()使用访问者模式来显示状态机的结构

    4. Cola-StateMachine实战

    数据库表字段还是继续使用状态管理艺术——借助Spring StateMachine驭服复杂应用逻辑_起名方面没有灵感的博客-CSDN博客的表字段,还有相关枚举也依然不变,不了解的可以去看看,文章里都有提供了,不再重复说明。

    首先,引入依赖

    <dependency>
        <groupId>com.alibaba.colagroupId>
        <artifactId>cola-component-statemachineartifactId>
        <version>4.3.1version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编写Cola-StateMachine配置

    /**
     * @description: 状态机配置
     * @author:lrk
     * @date: 2023/9/15
     */
    @Configuration
    @Slf4j
    public class ColaStatemachineConfig {
    
    
        @Bean
        public StateMachine orderStateMachine() {
            String ORDER_STATE_MACHINE = "orderStateMachine";
            // 第一步:生成一个状态机builder
            StateMachineBuilder builder = StateMachineBuilderFactory.create();
            // 第二步:定义状态
            // 待支付状态->待发货状态 —— 支付
            builder.externalTransition() // 外部流转
                    .from(OrderState.WAIT_PAYMENT)  // 起始状态
                    .to(OrderState.WAIT_DELIVER)  // 目标状态
                    .on(OrderStatusChangeEvent.PAYED)  // 事件
                    .when(checkCondition()) // 流转需要校验的条件,校验不通过不会进行doAction
                    .perform(doAction());  //执行流转操作 这个action 我们可以按自己所需修改
    
            // 待发货状态->待收货状态 —— 发货
            builder.externalTransition()
                    .from(OrderState.WAIT_DELIVER)
                    .to(OrderState.WAIT_RECEIVE)
                    .on(OrderStatusChangeEvent.DELIVERY)
                    .when(checkCondition())
                    .perform(doAction());
    
            // 待收货状态-> 完成 —— 收货
            builder.externalTransition()
                    .from(OrderState.WAIT_RECEIVE)
                    .to(OrderState.FINISH)
                    .on(OrderStatusChangeEvent.RECEIVED)
                    .when(checkCondition())
                    .perform(doAction());
            // 创建状态机
            StateMachine orderStateMachine = builder.build(ORDER_STATE_MACHINE);
            String uml = orderStateMachine.generatePlantUML();
            log.info("{}", uml);
            return orderStateMachine;
        }
    	
        
        private Condition checkCondition() {
            return (ctx) -> {
                log.info("checkCondition:{}", JSONUtil.toJsonStr(ctx));
                return true;
            };
        }
    
        private Action doAction() {
            return (from, to, event, order) -> {
                log.info(" 正在操作 " + order.getId() + " from:" + from + " to:" + to + " on:" + event);
                // 获取当前订单状态
                Integer status = order.getStatus();
                // 校验状态是否合法
                if (!status.equals(from.getValue())) {
                    throw new BusinessException(ErrorCode.OPERATION_ERROR);
                }
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    image-20230916001317511

    编写Service层

    @Service
    @Slf4j
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order>
            implements OrderService {
    
        @Resource
        private StateMachine<OrderState, OrderStatusChangeEvent, Order> orderStateMachine;
    
        @Override
        public Order create() {
            Order order = new Order();
            order.setName("小明" + UUID.randomUUID());
            order.setStatus(OrderState.WAIT_PAYMENT.getValue());
            this.save(order);
            return order;
        }
    
        @Override
        public Order pay(int id) {
            return orderOperation(id, OrderState.WAIT_PAYMENT, OrderStatusChangeEvent.PAYED);
        }
    
    
        @Override
        public Order deliver(int id) {
            return orderOperation(id, OrderState.WAIT_DELIVER, OrderStatusChangeEvent.DELIVERY);
        }
    
        @Override
        public Order receive(int id) {
            return orderOperation(id, OrderState.WAIT_RECEIVE, OrderStatusChangeEvent.RECEIVED);
        }
    
        @Override
        public List<Order> getOrders() {
            return this.list();
        }
    
        private Order orderOperation(int id, OrderState orderState, OrderStatusChangeEvent orderStatusChangeEvent) {
            String machineId = orderStateMachine.getMachineId();
            log.info("订单状态机:{}", machineId);
            Order order = this.getById(id);
            OrderState orderState1 = orderStateMachine.fireEvent(orderState, orderStatusChangeEvent, order);
            log.info("订单状态:{}", orderState1);
            order.setStatus(orderState1.getValue());
            this.updateById(order);
            return this.getById(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    可以看到,Service层只需要提供当前订单的下状态、订单的事件和订单的详细信息即可由状态机计算出订单的下一个状态,然后完成更新。

    image-20230916001114532

    image-20230916001149424


    5. 其他

    前面实战是外部状态的流转(也就是单个起始状态):起始状态为WAIT_PAYMENT,结束状态为WAIT_DELIVER,当事件为PAYED,并且满足checkCondition()的时候执行doAction(),成功返回WAIT_DELIVER,否则返回WAIT_PAYMENT

    builder.externalTransition()
        .from(OrderState.WAIT_PAYMENT)
        .to(OrderState.WAIT_DELIVER)
        .on(OrderStatusChangeEvent.PAYED)
        .when(checkCondition())
        .perform(doAction());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Cola-StateMachine还有内部状态流转:这个内部转换发生在**WAIT_PAYMENT状态下,当发生PAYED时执行状态流转,当满足checkCondition()时,执行doAction,执行成功则返回状态WAIT_PAYMENT**

    builder.internalTransition()
        .within(OrderState.WAIT_PAYMENT)
        .on(OrderStatusChangeEvent.PAYED)
        .when(checkCondition())
        .perform(doAction());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    除此之外,还有外部状态流转(多个起始状态):起始状态**WAIT_PAYMENTWAIT_DELIVER,结束状态FINISH,当发生RECEIVED时执行状态流转,当满足checkCondition()时,执行doAction,执行成功则返回状态FINISH**,否则返回对应起始状态

    builder.externalTransitions()
        .fromAmong(OrderState.WAIT_PAYMENT, OrderState.WAIT_DELIVER)
        .to(OrderState.FINISH)
        .on(OrderEvent.RECEIVED)
        .when(checkCondition())
        .perform(doAction());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    参考:

    1. 管理订单状态,该上状态机吗?轻量级状态机COLA StateMachine保姆级入门教程_cola状态机_倾听铃的声的博客-CSDN博客
    2. 状态机-cola-DSL_cola 状态机_盖茨比.丁的博客-CSDN博客
    3. 实现一个状态机引擎,教你看清DSL的本质_张建飞(Frank)的博客-CSDN博客
    4. alibaba/COLA: 🥤 COLA: Clean Object-oriented & Layered Architecture (github.com)
    5. COLA中的cola-statemachine状态机理解与使用例_cola状态机_肇小天的博客-CSDN博客
  • 相关阅读:
    CSS文本粒子动画特效之爱心粒子文字特效-Canvas
    手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(一) - 介绍
    zookeeper节点类型
    CentOS Install Passenger for ROR
    HDU 3549 — Flow Problem 入门题
    PHP-composer安装扩展安装,批量操作合并pdf
    K_A08_002 基于 STM32等单片机驱动MAX1508模块按键控制直流电机正反转加减速启停
    帮助文档Api
    【Java】网络层协议IP协议
    MongoDB CRUD操作:地理位置查询
  • 原文地址:https://blog.csdn.net/weixin_51146329/article/details/133010615