【架構設計】無狀態狀態機在程式碼中的實踐

一懶眾衫小QAQ 發表於 2021-10-15

無狀態狀態機

一:前言

​ 在專案中經常有一些工單,申請之類需要對狀態進行流轉。這種需求一般都是滿足什麼條件然後就翻轉狀態。這些流程結構相似得邏輯,感覺可以抽象處理。用一個通用得結構處理,可以讓系統更加整潔,程式碼邏輯更加單一。

​ 發現阿里開源一種,輕量級得無狀態狀態機得元件。仔細研究一下,確實很適合這種場景下對程式碼邏輯得解耦,相比於if-else程式碼更加讓人容易理解,也更加優雅。

二:狀態機的模型

  • State:狀態
  • Event:事件,狀態由事件觸發,引起變化
  • Transition:流轉,表示從一個狀態到另一個狀態
  • External Transition:外部流轉,兩個不同狀態之間的流轉
  • Internal Transition:內部流轉,同一個狀態之間的流轉
  • Condition:條件,表示是否允許到達某個狀態
  • Action:動作,到達某個狀態之後,可以做什麼
  • StateMachine:狀態機

三:怎麼使用

​ 這裡我們假設幾個簡單的邏輯需要處理:

  • 我們需要從待稽核的狀態需要流轉到稽核通過的狀態,稽核前需要做一個稽核校驗,稽核通過後需要傳送簡訊通知使用者。

外部狀態流轉實現:

  1. 定義狀態列舉(待稽核,稽核通過,拒絕)
 static enum States {
    WAITE, PASS, REJECT
}
  1. 定義一個事件列舉,稽核事件/稽核拒絕/提交申請/內部狀態流轉
static enum Events {
   AUDIT, AUDIT_REJECT, COMMIT, COMPLETE_INFORMATION
}

// 上下文物件
static class Context{
   String operator = "flw";
   String entityId = "7758258";
 }
  1. 按照上面需求配置一個狀態機
   StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()    // 外部狀態流轉
                .from(States.WAITE)     // 起始狀態:待稽核
                .to(States.PASS)        // 目的狀態:稽核通過
                .on(Events.AUDIT)       // 事件:稽核事件
                .when(checkCondition()) // 流轉需要校驗的條件,校驗不通過不會進行doAction
                .perform(doAction());   // 執行流轉操作

   StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
   // 列印狀態機裡面的流程流轉圖譜
   stateMachine.showStateMachine();

   private Condition<StateMachineMyTest.Context> checkCondition() {
        return (ctx) -> {return true;}; // 預設返回true
    }

    private Action<StateMachineMyTest.States, StateMachineMyTest.Events, StateMachineMyTest.Context> doAction() {
        return (from, to, event, ctx)->{
            System.out.println(ctx.operator+" is operating "+ctx.entityId+" from:"+from+" to:"+to+" on:"+event);
        };
    }

執行狀態機的列印圖譜結果:

-----StateMachine:TestStateMachine-------
State:PASS
State:WAITE
    Transition:WAITE-[AUDIT, EXTERNAL]->PASS
------------------------
  1. 執行狀態機
// 通過狀態機執行 待稽核狀態執行稽核操作,
States target = stateMachine.fireEvent(States.WAITE, Events.AUDIT, new Context());
Assert.assertEquals(States.PASS, target); // 成功就會返回目標狀態機,校驗失敗就會返回原來的狀態

執行結果:

flw is operating 7758258 from:WAITE to:PASS on:AUDIT

如果 checkCondition 返回false ,就不會執行doAction 操作。

內部狀態流轉實現

  • 假設現在只是使用者補全資料,只需要進行一些更新資料操作,不需要狀態流轉。這種需求可以通過內部狀態流轉實現
 @Test
    public void testExternalNormal(){
        StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()// 內部流轉
                .from(States.WAITE)
                .to(States.PASS)
                .on(Events.COMPLETE_INFORMATION)
                .when(checkCondition())
                .perform(doAction());

        StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
        // 列印狀態機裡面的流程流轉圖譜
        stateMachine.showStateMachine();
        // 通過狀態機執行 待稽核狀態執行稽核操作,
        States target = stateMachine.fireEvent(States.WAITE, Events.AUDIT, new Context());
        Assert.assertEquals(States.WAITE, target);
    }

執行結果:

-----StateMachine:TestStateMachine-------
State:PASS
State:WAITE
    Transition:WAITE-[COMPLETE_INFORMATION, EXTERNAL]->PASS
------------------------

對於方法裡面的異常也會丟擲來,也是支援事務操作。

整體的流程圖如下:

image-20211015223159331

四:總結

​ 狀態機非常輕量,支援事務操作,對於狀態流轉比較對的場景邏輯解耦還是比較優雅的。

連結:https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine