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

2023-09-19 07:31:13

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<S, E, C> externalTransition()用于一个流转的构建器
ExternalTransitionsBuilder<S, E, C> externalTransitions()用于多个流转的构建器
InternalTransitionBuilder<S, E, C> internalTransition()开始构建内部流转
StateMachine<S, E, C> 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.cola</groupId>
    <artifactId>cola-component-statemachine</artifactId>
    <version>4.3.1</version>
</dependency>

编写Cola-StateMachine配置

/**
 * @description: 状态机配置
 * @author:lrk
 * @date: 2023/9/15
 */
@Configuration
@Slf4j
public class ColaStatemachineConfig {


    @Bean
    public StateMachine<OrderState, OrderStatusChangeEvent, Order> orderStateMachine() {
        String ORDER_STATE_MACHINE = "orderStateMachine";
        // 第一步:生成一个状态机builder
        StateMachineBuilder<OrderState, OrderStatusChangeEvent, Order> 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<OrderState, OrderStatusChangeEvent, Order> orderStateMachine = builder.build(ORDER_STATE_MACHINE);
        String uml = orderStateMachine.generatePlantUML();
        log.info("{}", uml);
        return orderStateMachine;
    }
	
    
    private Condition<Order> checkCondition() {
        return (ctx) -> {
            log.info("checkCondition:{}", JSONUtil.toJsonStr(ctx));
            return true;
        };
    }

    private Action<OrderState, OrderStatusChangeEvent, Order> 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);
            }
        };
    }
}

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

可以看到,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());

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

builder.internalTransition()
    .within(OrderState.WAIT_PAYMENT)
    .on(OrderStatusChangeEvent.PAYED)
    .when(checkCondition())
    .perform(doAction());

除此之外,还有外部状态流转(多个起始状态):起始状态**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. 管理订单状态,该上状态机吗?轻量级状态机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博客
更多推荐

实战演练 | Navicat 常用功能之转储与运行 SQL 文件

数据库管理工作中,"转储SQL文件"和"运行SQL文件"是两个极为常见操作。一般来说,用户使用数据库管理工具或命令行工具来完成。Navicat管理开发工具中的“转储SQL文件”和“运行SQL文件”功能具有直观易用的界面、多种文件格式支持、数据备份和恢复功能、SQL编辑器、数据传输功能等多方面的优势,可以帮助用户更方便地

现在进入广告行业好做吗?

广告行业真的很好,大家快来……在这里你可以无限发挥你的创意和想象力,有趣的同事,不刻板的工作内容,与爱豆合作,偶尔见见明星,出入城市CBD,一身名牌,精美PPT挥斥方遒,轻松出几个炸裂的创意,戛纳获奖,满足感爆棚……以上这些,纯属想象!!做广告是很苦滴,这个苦包括精神和身体两个方面。现在如果没做好吃苦准备的,千万别来。

26 WEB漏洞-XSS跨站之订单及Shell箱子反杀记

目录xss平台及工具使用session与Cookie获取问题演示案例某营销订单系统XSS盲打_平台某Shell箱子系统XSS盲打_工具其他参考应用案例-后台权限维持工具Http/s数据包提交Postman使用xss平台及工具使用凡是有数据交互的地方,前端是接收数据的,后端是要把这个数据进行显示的,在这个过程中就符合了x

springmvc之自定义注解-->自定义注解简介,基本案例和aop自定义注解

自定义注解简介自定义注解基本案例aop自定义注解1.自定义注解简介1.基本注解2.元注解3.自定义注解3.1.标记注解3.2.元数据注解3.3.自定义注解语法:@interfaceJDK基本注解@Override重写@SuppressWarnings(value="unchecked")压制编辑器警告JDK元注解@Re

ns2无线局域网隐藏节点仿真实验

ns2无线局域网隐藏节点仿真实验实验内容实验原理实验过程相关模块安装仿真模块问题总结问题一问题二问题三实验内容无线网络与移动技术第二次实验,用ns2完成无线局域网隐藏节点仿真实验。实验原理隐藏节点指在接收节点的覆盖范围内而在发送节点的覆盖范围外的节点。由于听不到发送节点的发送,隐藏节点可能向相同的接收节点发送分组,导致

【动态规划刷题 16】最长等差数列 (有难度) && 等差数列划分 II - 子序列

1027.最长等差数列https://leetcode.cn/problems/longest-arithmetic-subsequence/给你一个整数数组nums,返回nums中最长等差子序列的长度。回想一下,nums的子序列是一个列表nums[i1],nums[i2],…,nums[ik],且0<=i1<i2<…

AOSP Android 系统源码编译出的framework.jar和android.jar之间的区别

简介AOSP(AndroidOpenSourceProject)编译出的android.jar和framework.jar都是Android平台开发中的重要组件,但它们有不同的作用和用途:android.jar:用途:android.jar包含了AndroidAPI的定义,它是Android应用程序开发的核心库。开发者

【Hadoop】HDFS API 操作大全

🍁博主"开着拖拉机回家"带您GotoNewWorld.✨🍁🦄个人主页——🎐开着拖拉机回家_Linux,大数据运维-CSDN博客🎐✨🍁🪁🍁希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥🪁🍁🪁🍁🪁🍁🪁🍁🪁🍁🪁🍁🪁🍁🪁🪁🍁🪁🍁🪁🍁🪁🍁🪁🍁�

多台服务器sessionId共享

目录多台服务器sessionId共享解决方案:ASP.NETCore参考代码(NET7):登录处理登录(请求)过滤器过滤器使用BaseController多台服务器sessionId共享sessionid是服务器首次与浏览器创建连接时,生成的id值,存入浏览器端cookie中,值加密的,下次请求时,浏览器自动带上ses

Flutter——启动页白屏的优化

flutter启动页白屏的优化:使用图片替代白屏结构图核心的代码如上图,修改两个launch_background.xml里的代码为:<item><bitmapandroid:gravity="center"android:src="@mipmap/ic_launcher"/></item>@mipmap/ic_lau

探讨代理IP与Socks5代理在跨界电商中的网络安全应用

在数字化时代,跨界电商已经成为了商业世界中的一大趋势。然而,跨越国界的电商活动也伴随着网络安全挑战。本文将讨论如何利用代理IP和Socks5代理技术来提高跨界电商中的网络安全,同时也探讨了与游戏相关的爬虫应用。1.代理IP和Socks5代理的基本概念1.1代理IP代理IP是一种网络技术,允许用户在访问互联网时使用中间服

热文推荐