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

来源:https://www.cnblogs.com/coderacademy/p/18067668
-Advertisement-
Play Games

本文主要介紹了設計模式中的狀態模式,然後在這個基礎上介紹了Spring狀態機相關的概念,並根據常見的訂單流轉場景,介紹了Spring狀態機的使用方式。 ...


引言

在複雜的應用程式設計中,尤其是那些涉及多個狀態變遷和業務流程式控制制的場景,有限狀態機(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、中間件、架構設計、面試題、程式員攻略等


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 摘要: 銀行卡歸屬地查詢介面是一種高效的方式,通過銀行卡號查詢銀行名稱、卡種、卡品牌以及發卡省份和城市等信息。本文將詳細介紹如何使用該介面,並附帶代碼說明。同時,也介紹了介面的特點和適用範圍,讓讀者能夠充分瞭解和運用該介面,方便快捷地獲取銀行卡發卡行所在地信息。 一、介面簡介 銀行卡歸屬地查詢介面是 ...
  • 1. 本篇文章目標 將下麵的excel中的寄存器表單讀入並構建一個字典 2. openpyxl的各種基本使用方法 2.1 打開工作簿 wb = openpyxl.load_workbook('test_workbook.xlsx') 2.2 獲取工作簿中工作表名字並得到工作表 ws = wb[wb. ...
  • 拓展閱讀 linux Shell 命令行-00-intro 入門介紹 linux Shell 命令行-02-var 變數 linux Shell 命令行-03-array 數組 linux Shell 命令行-04-operator 操作符 linux Shell 命令行-05-test 驗證是否符 ...
  • 在之前的多線程系列文章中,我們陸陸續續的介紹了Thread線程類相關的知識和用法,其實在Thread類上還有一層ThreadGroup類,也就是線程組。 ...
  • 2024年3月4日,官方宣佈推出 Claude 3 模型系列,它在廣泛的認知任務中樹立了新的行業基準。該系列包括三個按能力遞增排序的最先進模型:Claude 3 Haiku、Claude 3 Sonnet 和 Claude 3 Opus。每個後續模型都提供越來越強大的性能,允許用戶為其特定應用選擇智 ...
  • 大家好,我是你們的老伙計秀才!今天帶來的是[深入淺出Java多線程]系列的第十一篇內容:AQS(*AbstractQueuedSynchronizer*)。大家覺得有用請點贊,喜歡請關註!秀才在此謝過大家了!!! ...
  • Qt 是一個跨平臺C++圖形界面開發庫,利用Qt可以快速開發跨平臺窗體應用程式,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹如何運用`QNetworkAccessManager`組件實現Web網頁訪問。QNetworkAccessMana... ...
  • 前言 關於動態代理的一些知識,以及cglib與jdk動態代理的區別,在這一篇已經介紹過,不熟悉的可以先看下。 本篇我們來學習一下cglib的FastClass機制,這是cglib與jdk動態代理的一個主要區別,也是一個面試考點。 我們知道jdk動態代理是使用InvocationHandler介面,在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...