Mastering Spring State Machine: Build Robust Order Workflows with Persistence

This article introduces the fundamentals of finite state machines, explains their four core concepts, demonstrates how to model order workflows with state diagrams, and provides a comprehensive guide to implementing, configuring, persisting, and testing Spring Statemachine in a Java backend, including solutions for exception handling and AOP enhancements.

21CTO
21CTO
21CTO
Mastering Spring State Machine: Build Robust Order Workflows with Persistence

What is a State Machine

Explain the concept of a state, using an automatic door with open and closed as examples. A finite state machine (FSM) is a mathematical model that describes a limited set of states and the transitions between them.

Four Core Concepts

State : a condition of the system, e.g., open or closed .

Event : a trigger that causes a transition, e.g., pressing the open button.

Action : the operation performed when an event occurs, usually implemented as a function.

Transition : the change from one state to another, e.g., the door opening process.

Finite State Machine (FSM)

An FSM consists of a set of states, an initial state, inputs, and a transition function that determines the next state based on the current state and input.

State Machine Diagram

To model a process you need six elements: start, end, current state, target state, action, and condition. Example: an order changes from Pending Payment to Pending Shipment .

Spring Statemachine

Overview

Spring Statemachine provides a framework for applying state‑machine concepts within Spring applications.

Features

Easy‑to‑use flat state machine for simple scenarios.

Hierarchical state machine structure for complex configurations.

Region support for advanced state configurations.

Support for triggers, transitions, guards, and actions.

Type‑safe configuration adapters.

Builder pattern for instantiation outside the Spring context.

Distributed state machine based on Zookeeper.

State machine event listeners.

UML modeling with Eclipse Papyrus.

Persist state to permanent storage.

Spring IoC integration.

Quick Start

Database table for orders:

CREATE TABLE `tb_order` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `order_code` varchar(128) COMMENT '订单编码',
    `status` smallint(3) COMMENT '订单状态',
    `name` varchar(64) COMMENT '订单名称',
    `price` decimal(12,2) COMMENT '价格',
    `delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标记,0未删除 1已删除',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间',
    `create_user_code` varchar(32) COMMENT '创建人',
    `update_user_code` varchar(32) COMMENT '更新人',
    `version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',
    `remark` varchar(64) COMMENT '备注',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

Dependencies:

<!-- redis persistence -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-redis</artifactId>
    <version>1.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

Define state and event enums:

public enum OrderStatus {
    WAIT_PAYMENT(1, "待支付"),
    WAIT_DELIVER(2, "待发货"),
    WAIT_RECEIVE(3, "待收货"),
    FINISH(4, "已完成");
    private Integer key;
    private String desc;
    OrderStatus(Integer key, String desc) { this.key = key; this.desc = desc; }
    public Integer getKey() { return key; }
    public String getDesc() { return desc; }
    public static OrderStatus getByKey(Integer key) {
        for (OrderStatus e : values()) {
            if (e.getKey().equals(key)) return e;
        }
        throw new RuntimeException("enum not exists.");
    }
}
public enum OrderStatusChangeEvent {
    PAYED, DELIVERY, RECEIVED;
}

Configure the state machine:

@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
        states.withStates()
            .initial(OrderStatus.WAIT_PAYMENT)
            .states(EnumSet.allOf(OrderStatus.class));
    }
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
        transitions
            .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
            .and()
            .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
            .and()
            .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
    }
}

Persistence can be in‑memory or Redis. Example of an in‑memory persister and Redis configuration (YAML) is provided in the source.

Controller and service layers show how to start the machine, restore state, send events, and handle results, including synchronized sendEvent methods.

Testing

Endpoints for creating an order, paying, delivering, and receiving are listed. Re‑paying an already paid order triggers an error, illustrated by the following screenshot:

Problems and Solutions

The state machine swallows exceptions; sendEvent always returns true. The article proposes storing execution results in ExtendedState variables, checking them before persisting, or using an AOP aspect to record success (1) or failure (0).

Example of a custom @LogResult annotation and corresponding aspect that writes the outcome into the state machine’s extended state is included.

Author: Xiao Lu
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

aopworkflowspringstate machinePersistence
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.