Spring狀態機(FSM),讓訂單狀態流轉如絲般順滑

码农Academy發表於2024-03-12

引言

在複雜的應用程式設計中,尤其是那些涉及多個狀態變遷和業務流程控制的場景,有限狀態機(Finite State Machine, FSM)是一種強大而有效的建模工具。Spring框架為此提供了Spring狀態機(Spring State Machine)這一元件,它允許開發者以一種宣告式且結構清晰的方式來管理和控制物件的狀態流轉。

提起Spring狀態機,可能有些小夥伴還比較陌生。當你聽到狀態機時,一定會聯想到狀態設計模式。確實,狀態機是狀態模式的一種實際運用,在工作流引擎、訂單系統等領域有大量的應用。在介紹狀態機之前,我們先來回顧一下狀態模式,以便更好地理解Spring狀態機的概念和應用。

狀態模式

狀態模式是一種行為設計模式,用於管理物件的狀態以及狀態之間的轉換。在狀態模式中,物件在不同的狀態下表現出不同的行為,而狀態的轉換是由外部條件觸發的。狀態模式將每個狀態封裝成一個獨立的類,並將狀態轉換的邏輯分散在這些狀態類中,從而使得狀態的管理和轉換變得簡單和靈活。

狀態模式通常由以下幾個要素組成:

  1. 上下文(Context):上下文是包含了狀態的物件,它定義了當前的狀態以及可以觸發狀態轉換的介面。上下文物件在不同的狀態下會呼叫相應狀態物件的方法來執行具體的行為。

  2. 抽象狀態(State):抽象狀態是一個介面或抽象類,定義了狀態物件的通用行為介面。具體的狀態類需要實現這個介面,並根據不同的狀態來實現具體的行為。

  3. 具體狀態(Concrete State):具體狀態是實現了抽象狀態介面的具體類,它實現了在特定狀態下物件的行為。每個具體狀態類負責管理該狀態下的行為和狀態轉換規則。

狀態模式結構圖

狀態模式使得物件在不同狀態下的行為更加清晰和可維護,同時也使得物件的狀態轉換邏輯更加靈活和可擴充套件。狀態模式常見於需要物件根據外部條件改變行為的場景,例如訂單狀態(如待提交,待發貨,已發貨,已簽收,已完結等狀態)的管理、工作流引擎中的狀態(例如提交,稽核中,駁回,稽核透過,稽核失敗等)管理。

我們以訂單狀態的流轉為例:

  • 首先我們定義一個訂單抽象狀態的介面
public interface OrderState {  
  
    void handlerOrder();  
}
  • 在定義具體的訂單狀態,以及對應的訂單狀態的行為
public class OrderSubmitState implements OrderState{  
    @Override  
    public void handlerOrder() {  
        System.out.println("訂單已提交");  
    }  
}

public class OrderOutboundState implements OrderState{  
  
    @Override  
    public void handlerOrder() {  
        System.out.println("訂單已出庫");  
    }  
}

public class OrderSignedState implements OrderState{  
    @Override  
    public void handlerOrder() {  
        System.out.println("訂單已簽收");  
    }  
}
  • 在定義一個狀態的上下文,用於維護當前狀態物件,以及提供狀態流轉的方法
public class OrderContext {  
  
    private OrderState orderState;  
  
    public void setOrderState(OrderState orderState){  
        this.orderState = orderState;  
    }  
  
    public void handleOrder(){  
        orderState.handlerOrder();  
    }  
}
  • 編寫具體業務,測試訂單狀態流轉
public class OrderStateTest {  
  
    public static void main(String[] args) {  
        OrderSubmitState orderSubmitState = new OrderSubmitState();  
        OrderContext orderContext = new OrderContext();  
        orderContext.setOrderState(orderSubmitState);  
        orderContext.handleOrder();  
  
        OrderOutboundState orderOutboundState = new OrderOutboundState();  
        orderContext.setOrderState(orderOutboundState);  
        orderContext.handleOrder();  
  
        OrderSignedState orderSignedState = new OrderSignedState();  
        orderContext.setOrderState(orderSignedState);  
        orderContext.handleOrder();  
    }  
}

執行結果如下:

image.png
使用狀態模式中的狀態類不僅能消除if-else邏輯校驗,在一定程度上也增強了程式碼的可讀性和可維護性。類似策略模式,但是狀態機模式跟策略模式還有很大的區別的。

  1. 狀態模式:

    • 關注物件在不同狀態下的行為和狀態之間的轉換。
    • 透過封裝每個狀態為單獨的類來實現狀態切換,使得每個狀態物件都能處理自己的行為。
    • 狀態之間的轉換通常是透過條件判斷或外部事件觸發的。
  2. 策略模式:

    • 關注物件在不同策略下的行為差異。
    • 將不同的演算法或策略封裝成單獨的類,使得它們可以互相替換,並且在執行時動態地選擇不同的策略。
    • 不涉及狀態轉換,而是更多地關注於執行特定行為時選擇合適的策略。

雖然兩種模式都涉及物件行為的管理,但它們的關注點和應用場景略有不同。

關於消除if-else的方案請參考:程式碼整潔之道(一)之最佳化if-else的8種方案

什麼是狀態機

狀態機,顧名思義,是一種數學模型,它透過定義一系列有限的狀態以及狀態之間的轉換規則來模擬現實世界或抽象系統的動態行為。每個狀態代表系統可能存在的條件或階段,而狀態間的轉換則是由特定的輸入(即事件)觸發的。例如,在電商應用中,訂單狀態可能會經歷建立、支付、打包、發貨、完成等多個狀態,每個狀態之間的轉變都由對應的業務動作觸發。

在狀態機中,有以下幾個基本概念:

  1. 狀態(State):系統處於的特定狀態,可以是任何抽象的狀態,如有限狀態機中的“開”、“關”狀態,或是更具體的狀態如“執行”、“暫停”、“停止”等。

  2. 事件(Event):導致狀態轉換髮生的觸發器或輸入,例如使用者的輸入、外部事件等。事件觸發狀態之間的轉換。

  3. 轉移(Transition):描述狀態之間的變化或轉換,即從一個狀態到另一個狀態的過程。轉移通常由特定的事件觸發,觸發特定的轉移規則。

  4. 動作(Action):在狀態轉換髮生時執行的動作或操作,可以是一些邏輯處理、計算、輸出等。動作可以與狀態轉移相關聯。

  5. 初始狀態(Initial State):系統的初始狀態,即系統啟動時所處的狀態。

  6. 終止狀態(Final State):狀態機執行完成後所達到的狀態,表示整個狀態機的結束。

狀態機可以分為有限狀態機(Finite State Machine,FSM)和無限狀態機(Infinite State Machine)兩種。有限狀態機是指狀態的數量是有限的,而無限狀態機則可以有無限多個狀態。在系統設計中,有限狀態機比較常見。

Spring狀態機原理

Spring狀態機建立在有限狀態機(FSM)的概念之上,提供了一種簡潔且靈活的方式來定義、管理和執行狀態機。它將狀態定義為Java物件,並透過配置來定義狀態之間的轉換規則。狀態轉換通常由外部事件觸發,我們可以根據業務邏輯定義不同的事件型別,並與狀態轉換關聯。Spring狀態機還提供了狀態監聽器,用於在狀態變化時執行特定的邏輯。同時,狀態機的狀態可以持久化到資料庫或其他儲存介質中,以便在系統重啟或故障恢復時保持狀態的一致性。

Spring狀態機核心主要包括以下三個關鍵元素:

  1. 狀態(State):定義了系統可能處於的各個狀態,如訂單狀態中的待支付、已支付等。

  2. 轉換(Transition):描述了在何種條件下,當接收到特定事件時,系統可以從一個狀態轉移到另一個狀態。例如,接收到“支付成功”事件時,訂單狀態從“待支付”轉變為“已支付”。

  3. 事件(Event):觸發狀態轉換的動作或者訊息,它是引起狀態機從當前狀態遷移到新狀態的原因。

接下來,我們將上述狀態模式中關於訂單狀態的示例轉換為狀態機實現。

Spring狀態機的使用

對於狀態機,Spring中封裝了一個元件spring-statemachine,直接引入即可。

引入依賴

<dependency>
	<groupId>org.springframework.statemachine</groupId>
	<artifactId>spring-statemachine-starter</artifactId>
	<version>2.2.1.RELEASE</version>
</dependency>

定義狀態機的狀態以及事件型別

在狀態機(Finite State Machine, FSM)的設計中,“定義狀態”和“定義轉換”是構建狀態機模型的基礎元素。

定義狀態(States): 狀態是狀態機的核心組成單元,代表了系統或物件在某一時刻可能存在的條件或模式。在狀態機中,每一個狀態都是系統可能處於的一種明確的條件或階段。例如,在一個簡單的咖啡機狀態機中,可能有的狀態包括“待機”、“磨豆”、“沖泡”和“完成”。每個狀態都是獨一無二的,且在任何給定時間,系統只能處於其中一個狀態。

定義轉換(Transitions): 轉換則是指狀態之間的轉變過程,它是狀態機模型動態性的體現。當一個外部事件(如使用者按下按鈕、接收到訊號、滿足特定條件等)觸發時,狀態機會從當前狀態轉移到另一個狀態。在定義轉換時,需要指出觸發轉換的事件(Event)以及事件發生時系統的響應,即從哪個狀態(Source State)轉到哪個狀態(Target State)。

/**
*訂單狀態
*/
public enum OrderStatusEnum {
    /**待提交*/
    DRAFT,
    /**待出庫*/
    SUBMITTED,
    /**已出庫*/
    DELIVERING,
    /**已簽收*/
    SIGNED,
    /**已完成*/
    FINISHED,
    ;
}

/**
* 訂單狀態流轉事件
*/
public enum OrderStatusOperateEventEnum {
    /**確認,已提交*/
    CONFIRMED,
    /**發貨*/
    DELIVERY,
    /**簽收*/
    RECEIVED,
    /**完成*/
    CONFIRMED_FINISH,
    ;
}

定義狀態機以及狀態流轉規則

狀態機配置類是在使用Spring State Machine或其他狀態機框架時的一個重要步驟,這個類主要用於定義狀態機的核心結構,包括狀態(states)、事件(events)、狀態之間的轉換規則(transitions),以及可能的狀態遷移動作和決策邏輯。

Spring State Machine中,建立狀態機配置類通常是透過繼承StateMachineConfigurerAdapter類來實現的。這個介面卡類提供了幾個模板方法,允許開發者重寫它們來配置狀態機的各種組成部分:

  1. 配置狀態configureStates(StateMachineStateConfigurer)): 在這個方法中,開發者定義狀態機中所有的狀態,包括初始狀態(initial state)和結束狀態(final/terminal states)。例如,定義狀態A、B、C,並指定狀態A作為初始狀態。

  2. 配置轉換configureTransitions(StateMachineTransitionConfigurer)): 在這裡,開發者描述狀態之間的轉換規則,也就是當某個事件(event)發生時,狀態機應如何從一個狀態轉移到另一個狀態。例如,當事件X發生時,狀態機從狀態A轉移到狀態B。

  3. 配置初始狀態configureInitialState(ConfigurableStateMachineInitializer)): 如果需要顯式指定狀態機啟動時的初始狀態,可以在該方法中設定。

@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusOperateEventEnum> {

    /**
     * 設定狀態機的狀態
     * StateMachineStateConfigurer 即 狀態機狀態配置
     * @param states 狀態機狀態
     * @throws Exception 異常
     */
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusOperateEventEnum> states) throws Exception {
        states.withStates()
                .initial(OrderStatusEnum.DRAFT)
                .end(OrderStatusEnum.FINISHED)
                .states(EnumSet.allOf(OrderStatusEnum.class));
    }

    /**
     * 設定狀態機與訂單狀態操作事件繫結
     * StateMachineTransitionConfigurer
     * @param transitions
     * @throws Exception
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusOperateEventEnum> transitions) throws Exception {
        transitions.withExternal().source(OrderStatusEnum.DRAFT).target(OrderStatusEnum.SUBMITTED)
                .event(OrderStatusOperateEventEnum.CONFIRMED)
                .and()
                .withExternal().source(OrderStatusEnum.SUBMITTED).target(OrderStatusEnum.DELIVERING)
                .event(OrderStatusOperateEventEnum.DELIVERY)
                .and()
                .withExternal().source(OrderStatusEnum.DELIVERING).target(OrderStatusEnum.SIGNED)
                .event(OrderStatusOperateEventEnum.RECEIVED)
                .and()
                .withExternal().source(OrderStatusEnum.SIGNED).target(OrderStatusEnum.FINISHED)
                .event(OrderStatusOperateEventEnum.CONFIRMED_FINISH);

    }
}

配置狀態機持久化

狀態機持久化是指將狀態機在某一時刻的狀態資訊儲存到資料庫、快取系統等中,使得即使在系統重啟、網路故障或程序終止等情況下,狀態機仍能從先前儲存的狀態繼續執行,而不是從初始狀態重新開始。

在業務場景中,例如訂單處理、工作流引擎、遊戲進度跟蹤等,狀態機通常用於表示某個實體在其生命週期內的狀態變遷。如果沒有持久化機制,一旦發生意外情況導致系統當機或重啟,未完成的狀態變遷將會丟失,這對於業務連續性和一致性是非常不利的。

狀態機持久化通常涉及以下幾個方面:

  1. 狀態記錄:記錄當前狀態機例項處於哪個狀態。
  2. 上下文資料:除了狀態外,可能還需要持久化與狀態關聯的上下文資料,例如觸發狀態變遷的事件引數、額外的狀態屬性等。
  3. 歷史軌跡:某些複雜場景下可能需要記錄狀態機的歷史變遷軌跡,以便於審計、回溯分析或錯誤恢復。
  4. 併發控制:在多執行緒或多節點環境下,狀態機的持久化還要考慮併發訪問和同步的問題。

Spring Statemachine 提供了與RedisMongoDB等資料儲存結合的持久化方案,可以將狀態機的狀態資訊序列化後儲存到Redis中。當狀態機需要恢復時,可以從儲存中讀取狀態資訊並重新構造狀態機例項,使其能夠從上次中斷的地方繼續執行流程。

@Configuration
public class OrderPersist {


    /**
     * 持久化配置
     * 在實際使用中,可以配合資料庫或者Redis等進行持久化操作
     * @return
     */
    @Bean
    public DefaultStateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister(){
        Map<OrderDO, StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum>> map = new HashMap();
        return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO>() {
            @Override
            public void write(StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> context, OrderDO order) throws Exception {
                //持久化操作
                map.put(order, context);
            }

            @Override
            public StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> read(OrderDO order) throws Exception {
                //從庫中或者redis中讀取order的狀態資訊
                return map.get(order);
            }
        });
    }
}    

定義狀態機監聽器

狀態機監聽器(State Machine Listener)是一種元件,它可以監聽並響應狀態機在執行過程中的各種事件,例如狀態變遷、進入或退出狀態、轉換被拒絕等。

Spring Statemachine中,監聽器可以透過實現StateMachineListener介面來定義。該介面提供了一系列回撥方法,如transitionTriggeredstateEnteredstateExited等,當狀態機觸發轉換、進入新狀態或離開舊狀態時,這些方法會被呼叫。同時,我們也可以透過註解實現監聽器。註解方式可以在類的方法上直接宣告該方法應該在何種狀態下被呼叫,簡化監聽器的編寫和配置。例如@OnTransition@OnTransitionEnd@OnTransitionStart

@Component
@WithStateMachine(name = "orderStateMachine")
public class OrderStatusListener {

    @OnTransition(source = "DRAFT", target = "SUBMITTED")
    public boolean payTransition(Message<OrderStatusOperateEventEnum> message) {
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.SUBMITTED);
        System.out.println(String.format("出庫訂單[%s]確認,狀態機資訊:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }

    @OnTransition(source = "SUBMITTED", target = "DELIVERING")
    public boolean deliverTransition(Message<OrderStatusOperateEventEnum> message) {
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.DELIVERING);
        System.out.println(String.format("出庫訂單[%s]發貨出庫,狀態機資訊:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }

    @OnTransition(source = "DELIVERING", target = "SIGNED")
    public boolean receiveTransition(Message<OrderStatusOperateEventEnum> message){
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.SIGNED);
        System.out.println(String.format("出庫訂單[%s]簽收,狀態機資訊:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }

    @OnTransition(source = "SIGNED", target = "FINISHED")
    public boolean finishTransition(Message<OrderStatusOperateEventEnum> message){
        OrderDO order = (OrderDO) message.getHeaders().get("order");
        order.setOrderStatusEnum(OrderStatusEnum.FINISHED);
        System.out.println(String.format("出庫訂單[%s]完成,狀態機資訊:%s", order.getOrderNo(), message.getHeaders()));
        return true;
    }
}

而監聽器需要監聽到狀態流轉的事件才會發揮他的作用,才能監聽到某個狀態事件之後,完成狀態的變更。

@Component
public class StateEventUtil {

    private StateMachine<OrderStatusEnum, OrderStatusOperateEventEnum> orderStateMachine;

    private StateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister;

    /**
     * 傳送狀態轉換事件
     *  synchronized修飾保證這個方法是執行緒安全的
     * @param message
     * @return
     */
    public synchronized boolean sendEvent(Message<OrderStatusOperateEventEnum> message) {
        boolean result = false;
        try {
            //啟動狀態機
            orderStateMachine.start();
            OrderDO order = (OrderDO) message.getHeaders().get("order");
            //嘗試恢復狀態機狀態
            stateMachinePersister.restore(orderStateMachine, order);
            result = orderStateMachine.sendEvent(message);
            //持久化狀態機狀態
            stateMachinePersister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (Objects.nonNull(message)) {
                OrderDO order = (OrderDO) message.getHeaders().get("order");
                if (Objects.nonNull(order) && Objects.equals(order.getOrderStatusEnum(), OrderStatusEnum.FINISHED)) {
                    orderStateMachine.stop();
                }
            }
        }
        return result;
    }

    @Autowired
    public void setOrderStateMachine(StateMachine<OrderStatusEnum, OrderStatusOperateEventEnum> orderStateMachine) {
        this.orderStateMachine = orderStateMachine;
    }

    @Autowired
    public void setStateMachinePersister(StateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister) {
        this.stateMachinePersister = stateMachinePersister;
    }
}

到這裡,我們的狀態機就定義好了,下面我們就可以在業務程式碼中使用狀態機完成的訂單狀態的流轉。

業務程式碼使用

@Service
public class OrderServiceImpl implements IOrderService {

    private StateEventUtil stateEventUtil;

    private static final AtomicInteger ID_COUNTER = new AtomicInteger(0);

    private static final Map<Long, OrderDO> ORDER_MAP = new ConcurrentHashMap<>();

    /**
     * 建立新訂單
     *
     * @param orderDO
     */
    @Override
    public Long createOrder(OrderDO orderDO) {
        long orderId = ID_COUNTER.incrementAndGet();
        orderDO.setOrderId(orderId);
        orderDO.setOrderNo("OC20240306" + orderId);
        orderDO.setOrderStatusEnum(OrderStatusEnum.DRAFT);
        ORDER_MAP.put(orderId, orderDO);
        System.out.println(String.format("訂單[%s]建立成功:", orderDO.getOrderNo()));
        return orderId;
    }

    /**
     * 確認訂單
     *
     * @param orderId
     */
    @Override
    public void confirmOrder(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("確認訂單,訂單號:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.CONFIRMED).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 確認訂單失敗, 狀態異常,訂單號:" + order.getOrderNo());
        }
    }

    /**
     * 訂單發貨
     *
     * @param orderId
     */
    @Override
    public void deliver(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("訂單出庫,訂單號:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.DELIVERY).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 訂單出庫失敗, 狀態異常,訂單號:" + order.getOrderNo());
        }
    }

    /**
     * 簽收訂單
     *
     * @param orderId
     */
    @Override
    public void signOrder(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("訂單簽收,訂單號:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.RECEIVED).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 訂單簽收失敗, 狀態異常,訂單號:" + order.getOrderNo());
        }
    }

    /**
     * 確認完成
     *
     * @param orderId
     */
    @Override
    public void finishOrder(Long orderId) {
        OrderDO order = ORDER_MAP.get(orderId);
        System.out.println("訂單完成,訂單號:" + order.getOrderNo());
        Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.CONFIRMED_FINISH).
                setHeader("order", order).build();
        if (!stateEventUtil.sendEvent(message)) {
            System.out.println(" 訂單完成失敗, 狀態異常,訂單號:" + order.getOrderNo());
        }
    }

    /**
     * 獲取所有訂單資訊
     */
    @Override
    public List<OrderDO> listOrders() {
        return new ArrayList<>(ORDER_MAP.values());
    }

    @Autowired
    public void setStateEventUtil(StateEventUtil stateEventUtil) {
        this.stateEventUtil = stateEventUtil;
    }
}

我們在定義一個介面,模擬訂單的狀態流轉:

@RestController
public class OrderController {

    private IOrderService orderService;

    @GetMapping("testOrderStatusMachine")
    public void testOrderStatusMachine(){
        Long orderId1 = orderService.createOrder(new OrderDO());
        Long orderId2 = orderService.createOrder(new OrderDO());

        orderService.confirmOrder(orderId1);
        new Thread("客戶執行緒"){
            @Override
            public void run() {
                orderService.deliver(orderId1);
                orderService.signOrder(orderId1);
                orderService.finishOrder(orderId1);
            }
        }.start();

        orderService.confirmOrder(orderId2);
        orderService.deliver(orderId2);
        orderService.signOrder(orderId2);
        orderService.finishOrder(orderId2);

        System.out.println("全部訂單狀態:" + orderService.listOrders());


    }

    @Autowired
    public void setOrderService(IOrderService orderService) {
        this.orderService = orderService;
    }
}

我們呼叫介面:
image.png

我們在日誌中可以看到訂單狀態在狀態機的控制下,流轉的很絲滑。。。

注意事項

  • 一致性保證:確保狀態機的配置正確反映了業務邏輯,並保持其在併發環境下的狀態一致性。

  • 異常處理:在狀態轉換過程中可能出現異常情況,需要適當地捕獲和處理這些異常,防止狀態機進入無效狀態。

  • 監控與審計:在實際應用中,為了便於除錯和追溯,可以考慮整合日誌記錄或事件監聽器來記錄狀態機的每一次狀態變遷。

  • 擴充套件性與維護性:隨著業務的發展,狀態機的設計應當具有足夠的靈活性,以便於新增狀態或調整轉換規則。

一點思考

除了直接使用如Spring狀態機這樣的專門狀態管理工具外,還可以使用其他的哪些方法實現狀態機的功能呢?比如:

  1. 訊息佇列方式
    狀態的變更透過釋出和消費訊息來驅動。每當發生狀態變更所需的事件時,生產者將事件作為一個訊息釋出到特定的訊息佇列(Topic),而消費者則監聽這些訊息,根據訊息內容和業務規則對訂單狀態進行更新。這種方式有利於解耦各個服務,實現非同步處理,同時增強系統的伸縮性和容錯能力。

  2. 定時任務驅動
    使用定時任務定期檢查系統中的訂單狀態,根據預設的業務規則判斷是否滿足狀態變遷條件。比如,每隔一段時間執行一次Job,查詢資料庫中處於特定狀態的訂單,並決定是否進行狀態更新。這種方法適用於具有一定時效性的狀態變遷,但實時性相對較低,對於瞬時響應要求高的場景不太適用。

有關SpringBoot下幾種定時任務的實現方式請參考:玩轉SpringBoot:SpringBoot的幾種定時任務實現方式

  1. 規則引擎方式
    利用規則引擎(如DroolsLiteFlow等)實現狀態機,業務團隊可以直接在規則引擎中定義狀態及狀態之間的轉換規則,當新的事實資料(如訂單資訊)輸入到規則引擎時,引擎會自動匹配並執行相應的規則,觸發狀態改變。這種方式的優點在於業務規則高度集中,易於管理和修改,同時也具備較高的靈活性,能夠快速應對業務規則的變化。

SpringBoot下使用LiteFlow規則引擎請參考:輕鬆應對複雜業務邏輯:LiteFlow-編排式規則引擎框架的優勢

總結

Spring狀態機提供了一種強大的工具,使得在Java應用中實現複雜的業務流程變得更為簡潔和規範。不僅可以提升程式碼的可讀性和可維護性,還能有效降低不同模組之間的耦合度,提高系統的整體穩定性與健壯性。

本文已收錄於我的個人部落格:碼農Academy的部落格,專注分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中介軟體、架構設計、面試題、程式設計師攻略等

相關文章