如何使用Spring状态机优雅的管理复杂流程之间的状态变更

在我们的实际项目中经常会遇到一些多流程之间的状态变更问题,典型如订单状态的变更,如下所示的是事件与订单的状态变更的关系图:

每当触发一个事件之后订单的状态就发生改变,那么针对这种事件驱动状态变更的流程,我们如何优雅的管理这套流程中的事件与状态的关系并且做到扩展性高的要求呢?下面我们使用Spring的状态机来帮助我们实现这个过程。

1、认识Spring状态机

Spring状态机(Spring State Machine)是Spring Framework提供的一个模块,用于支持有限状态机(描述了一个系统在不同状态之间的转换以及触发这些转换的事件)的实现。

Spring状态机主要用于处理对象的状态变化和状态之间的转换。它提供了一种以声明性的方式定义状态和状态之间转换的机制,并能够处理各种事件触发的状态迁移。Spring转换机的核心是状态和事件,如订单状态变更流程中,用户支付之后订单的状态从待支付变更为代发货状态,这里用户支付是事件,待支付到待发货是状态。

2、实战Spring状态机实现订单状态变更

(1)添加maven依赖

<dependencies> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-web</artifactId> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.statemachine</groupId> 
            <artifactId>spring-statemachine-starter</artifactId> 
            <version>2.2.0.RELEASE</version> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-test</artifactId> 
            <scope>test</scope> 
            <exclusions> 
                <exclusion> 
                    <groupId>org.junit.vintage</groupId> 
                    <artifactId>junit-vintage-engine</artifactId> 
                </exclusion> 
            </exclusions> 
        </dependency> 
    </dependencies>

(2)定义状态

public enum OrderStatusEnum { 
    WAIT_PAY(1,"待支付"), 
    WAIT_DELIVER_GOODS(3, "待发货"), 
    WAIT_RECEIVE_GOODS(5,"待收货"), 
    TRANSACTION_SUCCESS(7, "交易完成"), 
    ; 
    private Integer status; 
    private String desc; 
    OrderStatusEnum(Integer status, String desc) { 
        this.status = status; 
        this.desc = desc; 
    } 
    public Integer getStatus() { 
        return status; 
    } 
    public String getDesc() { 
        return desc; 
    } 
}

(3)定义事件

public enum  OrderEventEnum { 
    USER_PAY("用户支付"), 
    WAREHOUSE_DELIVER_GOODS("仓库发货"), 
    USER_RECEIVE_GOODS("用户收货"), 
    ; 

    private String desc; 
    OrderEventEnum(String desc) { 
        this.desc = desc; 
    } 
    public String getDesc() { 
        return desc; 
    } 
}

(4)配置事件与状态的关系

@Configuration 
@EnableStateMachine(name = "springStateMachineConfig") 
public class SpringStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatusEnum, OrderEventEnum> { 
    @Override 
    public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderEventEnum> state) 
            throws Exception { 
        state.withStates() 
                .initial(OrderStatusEnum.WAIT_PAY)   //初始的状态 
                .states(Stream.of(OrderStatusEnum.values()).collect(Collectors.toSet())); 
    } 
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEventEnum> transitions) 
            throws Exception { 
        transitions. 
                withExternal(). 
                //状态从待支付---->待发货 
                source(OrderStatusEnum.WAIT_PAY).target(OrderStatusEnum.WAIT_DELIVER_GOODS). 
                //事件是用户支付 
                event(OrderEventEnum.USER_PAY). 
                and(). 
                withExternal(). 
                //状态从待发货---->待收货 
                source(OrderStatusEnum.WAIT_DELIVER_GOODS).target(OrderStatusEnum.WAIT_RECEIVE_GOODS). 
                //事件是仓库发货 
                event(OrderEventEnum.WAREHOUSE_DELIVER_GOODS). 
                and(). 
                withExternal(). 
                //状态从待收货---->交易成功 
                source(OrderStatusEnum.WAIT_RECEIVE_GOODS).target(OrderStatusEnum.TRANSACTION_SUCCESS). 
                //事件是用户确认收货 
                event(OrderEventEnum.USER_RECEIVE_GOODS); 
    } 
}

(5)监听具体的事件

@Component 
@WithStateMachine(name = "springStateMachineConfig") 
public class SpringStateMachineEventListener { 
    @OnTransition(target="WAIT_PAY") 
    public boolean create(Message<OrderEntity> order) { 
        System.out.println("订单创建,待支付"); 
        return true; 
    } 
    @OnTransition(source = "WAIT_PAY", target="WAIT_DELIVER_GOODS") 
    public boolean userPay(Message<OrderEntity> order) { 
        System.out.println("用户支付"); 
        return true; 
    } 
    @OnTransition(source = "WAIT_DELIVER_GOODS", target="WAIT_RECEIVE_GOODS") 
    public boolean warehourseDeliver(Message<OrderEntity> order) { 
        System.out.println("仓库发货"); 
        return true; 
    } 
    @OnTransition(source = "WAIT_RECEIVE_GOODS", target="TRANSACTION_SUCCESS") 
    public boolean userReceive(Message<OrderEntity> order) { 
        System.out.println("用户收货"); 
        return true; 
    } 
}

至此我们就完成核心代码的编写,主要过程是定义状态、定义事件、然后配置状态和事件的之间的关联关系,最后监听事件来完成状态变更。Spring状态机还有一些其他的功能(如动作和转换),有兴趣的朋友可以继续深入研究一下。

总结:

(1)在一些如工作流、订单处理等业务场景下可以使用Spring状态机,此时我们只需要把状态和事件进行绑定,通过事件驱动对状态变更。

(2)状态机除了Spring状态机之外还有其他比较成熟的状态机,如cola状态机。

(3)如果需要添加交易取消的状态,我们只需要添加状态、事件以及事件与状态的绑定关系,最后编写监听就是实现快速的加入到流程中。

7