狀態機的介紹和使用

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/08/01/17595684.html
-Advertisement-
Play Games

狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。狀態機,也就是 State Machine ,不是指一臺實際機器,而是指一個數學模型。說白了,一般就是指一張狀態轉換圖。 ...


1 狀態機簡介

1.1 定義

我們先來給出狀態機的基本定義。一句話:

狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。

先來解釋什麼是“狀態”( State )。現實事物是有不同狀態的,例如一個自動門,就有 open 和 closed 兩種狀態。我們通常所說的狀態機是有限狀態機,也就是被描述的事物的狀態的數量是有限個,例如自動門的狀態就是兩個 open 和 closed 。

狀態機,也就是 State Machine ,不是指一臺實際機器,而是指一個數學模型。說白了,一般就是指一張狀態轉換圖。例如,根據自動門的運行規則,我們可以抽象出下麵這麼一個圖。

自動門有兩個狀態,open 和 closed ,closed 狀態下,如果讀取開門信號,那麼狀態就會切換為 open 。open 狀態下如果讀取關門信號,狀態就會切換為 closed 。

狀態機的全稱是有限狀態自動機,自動兩個字也是包含重要含義的。給定一個狀態機,同時給定它的當前狀態以及輸入,那麼輸出狀態時可以明確的運算出來的。例如對於自動門,給定初始狀態 closed ,給定輸入“開門”,那麼下一個狀態時可以運算出來的。

這樣狀態機的基本定義我們就介紹完畢了。重覆一下:狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。

1.2 四大概念

下麵來給出狀態機的四大概念。

第一個是 State ,狀態。一個狀態機至少要包含兩個狀態。例如上面自動門的例子,有 open 和 closed 兩個狀態。

第二個是 Event ,事件。事件就是執行某個操作的觸發條件或者口令。對於自動門,“按下開門按鈕”就是一個事件。

第三個是 Action ,動作。事件發生以後要執行動作。例如事件是“按開門按鈕”,動作是“開門”。編程的時候,一個 Action 一般就對應一個函數。

第四個是 Transition ,變換。也就是從一個狀態變化為另一個狀態。例如“開門過程”就是一個變換。

2 DSL

2.1 DSL

DSL是一種工具,它的核心價值在於,它提供了一種手段,可以更加清晰地就系統某部分的意圖進行溝通。

這種清晰並非只是審美追求。一段代碼越容易看懂,就越容易發現錯誤,也就越容易對系統進行修改。因此,我們鼓勵變數名要有意義,文檔要寫清楚,代碼結構要寫清晰。基於同樣的理由,我們應該也鼓勵採用DSL。

按照定義來說,DSL是針對某一特定領域,具有受限表達性的一種電腦程式設計語言。

這一定義包含3個關鍵元素:

語言性(language nature):DSL是一種程式設計語言,因此它必須具備連貫的表達能力——不管是一個表達式還是多個表達式組合在一起。

受限的表達性(limited expressiveness):通用程式設計語言提供廣泛的能力:支持各種數據、控制,以及抽象結構。這些能力很有用,但也會讓語言難於學習和使用。DSL只支持特定領域所需要特性的最小集。使用DSL,無法構建一個完整的系統,相反,卻可以解決系統某一方面的問題。

針對領域(domain focus):只有在一個明確的小領域下,這種能力有限的語言才會有用。這個領域才使得這種語言值得使用。

比如正則表達式,/\d{3}-\d{3}-\d{4}/就是一個典型的DSL,解決的是字元串匹配這個特定領域的問題。

2.2 DSL的分類

按照類型,DSL可以分為三類:內部DSL(Internal DSL)、外部DSL(External DSL)、以及語言工作台(Language Workbench)。

Internal DSL是一種通用語言的特定用法。用內部DSL寫成的腳本是一段合法的程式,但是它具有特定的風格,而且只用到了語言的一部分特性,用於處理整個系統一個小方面的問題。 用這種DSL寫出的程式有一種自定義語言的風格,與其所使用的宿主語言有所區別。例如我們的狀態機就是Internal DSL,它不支持腳本配置,使用的時候還是Java語言,但並不妨礙它也是DSL。

builder.externalTransition()
                .from(States.STATE1)
                .to(States.STATE2)
                .on(Events.EVENT1)
                .when(checkCondition())
                .perform(doAction());





External DSL是一種“不同於應用系統主要使用語言”的語言。外部DSL通常採用自定義語法,不過選擇其他語言的語法也很常見(XML就是一個常見選 擇)。比如像Struts和Hibernate這樣的系統所使用的XML配置文件。

Workbench是一個專用的IDE,簡單點說,工作台是DSL的產品化和可視化形態。

三個類別DSL從前往後是有一種遞進關係,Internal DSL最簡單,實現成本也低,但是不支持“外部配置”。Workbench不僅實現了配置化,還實現了可視化,但是實現成本也最高。他們的關係如下圖所示:

2.3 DSL示例

2.3.1 內部DSL示例

HTML: 通過自然語言編寫

在Groovy中,通過DSL可以用易讀的寫法生成XML

def s = new StringWriter()
def xml = new MarkupBuilder(s)
xml.html{
    head{
        title("Hello - DSL")
        script(ahref:"https://xxxx.com/vue.js")
        meta(author:"marui116")
    }
    body{
        p("JD-ILT-ITMS")
    }
}
println s.toString()





最後將生成

<html>
  <head>
    <title>Hello - DSL</title>
    <script ahref='https://xxxx.com/vue.js' />
    <meta author='marui116' />
  </head>
  <body>
    <p>JD-ILT-ITMS</p>
  </body>
</html>





MarkupBuilder的作用說明:

A helper class for creating XML or HTML markup. The builder supports various 'pretty printed' formats.
Example:
  new MarkupBuilder().root {
    a( a1:'one' ) {
      b { mkp.yield( '3 < 5' ) }
      c( a2:'two', 'blah' )
    }
  }
  
Will print the following to System.out:
  <root>
    <a a1='one'>
      <b>3 < 5</b>
      <c a2='two'>blah</c>
    </a>
  </root>





這裡相對於Java這樣的動態語言,最為不同的就是xml.html這個並不存在的方法居然可以通過編譯並運行,它內部重寫了invokeMethod方法,併進行閉包遍歷,少寫了許多POJO對象,效率更高。

2.3.2 外部DSL

以plantUML為例,外部DSL不受限於宿主語言的語法,對用戶很友好,尤其是對於不懂宿主語言語法的用戶。但外部DSL的自定義語法需要有配套的語法分析器。常見的語法分析器有:YACC、ANTLR等。

https://github.com/plantuml/plantuml

https://plantuml.com/zh/

2.3.3 DSL & DDD(領域驅動)

DDD和DSL的融合有三點:面向領域、模型的組裝方式、分層架構演進。DSL 可以看作是在領域模型之上的一層外殼,可以顯著增強領域模型的能力。

它的價值主要有兩個,一是提升了開發人員的生產力,二是增進了開發人員與領域專家的溝通。外部 DSL 就是對領域模型的一種組裝方式。

3 狀態機實現的調研

3.1 Spring Statemachine

官網:https://spring.io/projects/spring-statemachine#learn

源碼:https://github.com/spring-projects/spring-statemachine

API:https://docs.spring.io/spring-statemachine/docs/3.2.0/api/

Spring Statemachine is a framework for application developers to use state machine concepts with Spring applications. Spring Statemachine 是應用程式開發人員在Spring應用程式中使用狀態機概念的框架。

Spring Statemachine 提供如下特色:

•Easy to use flat one level state machine for simple use cases.(易於使用的扁平單級狀態機,用於簡單的使用案例。)

•Hierarchical state machine structure to ease complex state configuration.(分層狀態機結構,以簡化複雜的狀態配置。)

•State machine regions to provide even more complex state configurations.(狀態機區域提供更複雜的狀態配置。)

•Usage of triggers, transitions, guards and actions.(使用觸發器、transitions、guards和actions。)

•Type safe configuration adapter.(應用安全的配置適配器。)

•Builder pattern for easy instantiation for use outside of Spring Application context(用於在Spring Application上下文之外使用的簡單實例化的生成器模式)

•Recipes for usual use cases(通常用例的手冊)

•Distributed state machine based on a Zookeeper State machine event listeners.(基於Zookeeper的分散式狀態機狀態機事件監聽器。)

•UML Eclipse Papyrus modeling.(UML Eclipse Papyrus 建模)

•Store machine config in a persistent storage.(存儲狀態機配置到持久層)

•Spring IOC integration to associate beans with a state machine.(Spring IOC集成將bean與狀態機關聯起來)

Spring StateMachine提供了papyrus的Eclipse Plugin,用來輔助構建狀態機。

更多Eclipse建模插件可參見文檔:https://docs.spring.io/spring-statemachine/docs/3.2.0/reference/#sm-papyrus

Spring狀態機的配置、定義、事件、狀態擴展、上下文集成、安全性、錯誤處理等,可以參看如下文檔:

https://docs.spring.io/spring-statemachine/docs/3.2.0/reference/#statemachine

3.2 COLA狀態機DSL實現

COLA 是 Clean Object-Oriented and Layered Architecture的縮寫,代表“整潔面向對象分層架構”。 目前COLA已經發展到COLA v4。COLA提供了一個DDD落地的解決方案,其中包含了一個開源、簡單、輕量、性能極高的狀態機DSL實現,解決業務中的狀態流轉問題。

COLA狀態機組件實現一個僅支持簡單狀態流轉的狀態機,該狀態機的核心概念如下圖所示,主要包括:

1.State:狀態

2.Event:事件,狀態由事件觸發,引起變化

3.Transition:流轉,表示從一個狀態到另一個狀態

4.External Transition:外部流轉,兩個不同狀態之間的流轉

5.Internal Transition:內部流轉,同一個狀態之間的流轉

6.Condition:條件,表示是否允許到達某個狀態

7.Action:動作,到達某個狀態之後,可以做什麼

8.StateMachine:狀態機

整個狀態機的核心語義模型(Semantic Model):

4 狀態機DEMO

4.1 Spring狀態機示例

代碼地址:http://xingyun.jd.com/codingRoot/ilt/spring-statemachine-demo/

例如,起始節點為SI、結束節點為SF,起始節點後續有S1、S2、S3三個節點的簡單狀態機。

Spring Boot項目需引入Spring狀態機組件。

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>3.2.0</version>
</dependency>





4.1.1 構造狀態機

@Configuration
@EnableStateMachine
@Slf4j
public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
    /**
     * 定義初始節點、結束節點和狀態節點
     * @param states the {@link StateMachineStateConfigurer}
     * @throws Exception
     */
    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
        states.withStates()
            .initial("SI")
            .end("SF")
            .states(new HashSet<String>(Arrays.asList("S1", "S2", "S3")));
    }

    /**
     * 配置狀態節點的流向和事件
     * @param transitions the {@link StateMachineTransitionConfigurer}
     * @throws Exception
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
        transitions.withExternal()
                .source("SI").target("S1").event("E1").action(initAction())
                .and()
                .withExternal()
                .source("S1").target("S2").event("E2").action(s1Action())
                .and()
                .withExternal()
                .source("S2").target("SF").event("end");
    }

    /**
     * 初始節點到S1
     * @return
     */
    @Bean
    public Action<String, String> initAction() {
        return ctx -> log.info("Init Action -- DO: {}", ctx.getTarget().getId());
    }

    /**
     * S1到S2
     * @return
     */
    @Bean
    public Action<String, String> s1Action() {
        return ctx -> log.info("S1 Action -- DO: {}", ctx.getTarget().getId());
    }
}





4.1.2 狀態機狀態監聽器

@Component
@Slf4j
public class StateMachineListener extends StateMachineListenerAdapter<String, String> {
 
    @Override
    public void stateChanged(State from, State to) {
        log.info("Transitioned from {} to {}", from == null ? "none" : from.getId(), to.getId());
    }
}





4.1.3 狀態機配置

@Configuration
@Slf4j
public class StateMachineConfig implements WebMvcConfigurer {
    @Resource
    private StateMachine<String, String> stateMachine;

    @Resource
    private StateMachineListener stateMachineListener;

    @PostConstruct
    public void init() {
        stateMachine.addStateListener(stateMachineListener);
    }
}





4.1.4 介面示例

4.1.4.1 獲取狀態機狀態列表
@RequestMapping("info")
public String info() {
    return StringUtils.collectionToDelimitedString(
            stateMachine.getStates()
                    .stream()
                    .map(State::getId)
                    .collect(Collectors.toList()),
                    ",");
}





4.1.4.2 狀態機開啟

在對Spring狀態機進行事件操作之前,必須先開啟狀態機

@GetMapping("start")
public String start() {
    stateMachine.startReactively().block();
    return state();
}





4.1.4.3 事件操作
@PostMapping("event")
public String event(@RequestParam(name = "event") String event) {
    Message<String> message = MessageBuilder.withPayload(event).build();
    return stateMachine.sendEvent(Mono.just(message)).blockLast().getMessage().getPayload();
}





4.1.4.4 獲取狀態機當前狀態
@GetMapping("state")
public String state() {
    return Mono.defer(() -> Mono.justOrEmpty(stateMachine.getState().getId())).block();
}





4.1.4.5 一次狀態轉換的控制台輸出
: Completed initialization in 0 ms
: Transitioned from none to SI
: Init Action -- DO: S1
: Transitioned from SI to S1
: S1 Action -- DO: S2
: Transitioned from S1 to S2
: Transitioned from S2 to SF





可以看到,狀態從none到SI開始節點,再到S1、S2,然後S2通過E2事件到SF結束節點。

4.2 COLA狀態機示例

代碼地址:http://xingyun.jd.com/codingRoot/ilt/ilt-component-statemachine/

例如:iTMS中的運輸需求單的狀態目前有:待分配、已分配、運輸中、部分妥投、全部妥投、全部拒收、已取消。

4.2.1 構造狀態機

com.jd.ilt.component.statemachine.demo.component.statemachine.TransNeedStateMachine

StateMachineBuilder<TransNeedStatusEnum, TransNeedEventEnum, Context> builder = StateMachineBuilderFactory.create();

//  接單後,運輸需求單生成運輸規劃單
builder.externalTransition()
        .from(None)
        .to(UN_ASSIGN_CARRIER)
        .on(Create_Event)
        .when(checkCondition())
        .perform(doAction());

//  運輸規劃單生成調度單,調度單綁定服務商
builder.externalTransition()
        .from(UN_ASSIGN_CARRIER)
        .to(UN_ASSIGN_CAR)
        .on(Assign_Carrier_Event)
        .when(checkCondition())
        .perform(doAction());

//  服務商分配車輛、司機
builder.externalTransition()
        .from(UN_ASSIGN_CAR)
        .to(ASSIGNED_CAR)
        .on(Assign_Car_Event)
        .when(checkCondition())
        .perform(doAction());

//  貨物攬收
builder.externalTransition()
        .from(ASSIGNED_CAR)
        .to(PICKUPED)
        .on(Trans_Job_Status_Change_Event)
        .when(checkCondition())
        .perform(doAction());

//  攬收貨物更新到運輸中
builder.externalTransition()
        .from(ASSIGNED_CAR)
        .to(IN_TRANSIT)
        .on(Trans_Job_Status_Change_Event)
        .when(checkCondition())
        .perform(doAction());

//  運輸中更新到過海關
builder.externalTransition()
        .from(IN_TRANSIT)
        .to(PASS_CUSTOMS)
        .on(Trans_Job_Status_Change_Event)
        //  檢查是否需要過海關
        .when(isTransNeedPassCustoms())
        .perform(doAction());

//  妥投
builder.externalTransition()
        .from(PASS_CUSTOMS)
        .to(ALL_DELIVERIED)
        .on(All_Delivery_Event)
        .when(checkCondition())
        .perform(doAction());

// 車輛攬收、運輸、過海關的運輸狀態,都可以直接更新到妥投
Stream.of(PICKUPED, IN_TRANSIT, PASS_CUSTOMS)
        .forEach(status ->
                builder.externalTransition()
                        .from(status)
                        .to(ALL_DELIVERIED)
                        .on(Trans_Job_Status_Change_Event)
                        .when(checkCondition())
                        .perform(doAction())
        );

//  待分配、待派車、已派車可取消
Stream.of(UN_ASSIGN_CARRIER, UN_ASSIGN_CAR, ASSIGNED_CAR)
        .forEach(status ->
                builder.externalTransition()
                        .from(status)
                        .to(CANCELED)
                        .on(Order_Cancel_Event)
                        .when(checkCondition())
                        .perform(doAction())
        );

//  妥投、和取消可結束歸檔
Stream.of(ALL_DELIVERIED, CANCELED)
        .forEach(status ->
                builder.externalTransition()
                        .from(status)
                        .to(FINISH)
                        .on(Order_Finish)
                        .when(checkCondition())
                        .perform(doAction())
        );

stateMachine = builder.build("TransNeedStatusMachine");





從代碼中,可以方便的擴展狀態和對應的事件,狀態機自動進行業務狀態的流轉。生成的狀態流轉圖如下所示:

@startuml
None --> UN_ASSIGN_CARRIER : Create_Event
UN_ASSIGN_CARRIER --> UN_ASSIGN_CAR : Assign_Carrier_Event
UN_ASSIGN_CAR --> ASSIGNED_CAR : Assign_Car_Event
ASSIGNED_CAR --> CANCELED : Order_Cancel_Event
ASSIGNED_CAR --> PICKUPED : Trans_Job_Status_Change_Event
ASSIGNED_CAR --> IN_TRANSIT : Trans_Job_Status_Change_Event
IN_TRANSIT --> PASS_CUSTOMS : Trans_Job_Status_Change_Event
PASS_CUSTOMS --> ALL_DELIVERIED : Trans_Job_Status_Change_Event
PASS_CUSTOMS --> ALL_DELIVERIED : All_Delivery_Event
IN_TRANSIT --> ALL_DELIVERIED : Trans_Job_Status_Change_Event
ALL_DELIVERIED --> FINISH : Order_Finis
UN_ASSIGN_CAR --> CANCELED : Order_Cancel_Event
UN_ASSIGN_CARRIER --> CANCELED : Order_Cancel_Event
PICKUPED --> ALL_DELIVERIED : Trans_Job_Status_Change_Event
CANCELED --> FINISH : Order_Finis
@enduml





4.2.2 狀態機事件處理

/**
 * 一種是通過Event來進行事件分發,不同Event通過EventBus走不同的事件響應
* 另一種是在構造狀態機時,直接配置不同的Action
 * @return
 */
private Action<TransNeedStatusEnum, TransNeedEventEnum, Context> doAction() {
    log.info("do action");
    return (from, to, event, ctx) -> {
        log.info(ctx.getUserName()+" is operating trans need bill "+ctx.getTransNeedId()+" from:"+from+" to:"+to+" on:"+event);
        if (from != None) {
            TransNeed transNeed = ctx.getTransNeed();
            transNeed.setStatus(to.name());
            transNeed.setUpdateTime(LocalDateTime.now());
            transNeedService.update(transNeed);
        }

        eventBusService.invokeEvent(event, ctx);
    };
}





Event和EventBus簡單Demo示例:

/**
 * @author marui116
 * @version 1.0.0
 * @className TransNeedAssignCarrierEvent
 * @description TODO
* @date 2023/3/28 11:08
 */
@Component
@EventAnnonation(event = TransNeedEventEnum.Assign_Carrier_Event)
@Slf4j
public class TransNeedAssignCarrierEvent implements EventComponent {

    @Override
    public void invokeEvent(Context context) {
        log.info("分配了服務商,給服務商發郵件和簡訊,讓服務商安排");
    }
}





/**
 * @author marui116
 * @version 1.0.0
 * @className TransNeedAssignCarEvent
 * @description TODO
* @date 2023/3/28 11:05
 */
@Component
@EventAnnonation(event = TransNeedEventEnum.Assign_Car_Event)
@Slf4j
public class TransNeedAssignCarEvent implements EventComponent {
    @Override
    public void invokeEvent(Context context) {
        log.info("分配了車輛信息,給運單中心發送車輛信息");
    }
}





/**
 * @author marui116
 * @version 1.0.0
 * @className EventServiceImpl
 * @description TODO
* @date 2023/3/28 10:57
 */
@Service
public class EventBusServiceImpl implements EventBusService {
    @Resource
    private ApplicationContextUtil applicationContextUtil;

    private Map<TransNeedEventEnum, EventComponent> eventComponentMap = new ConcurrentHashMap<>();

    @PostConstruct
    private void init() {
        ApplicationContext context = applicationContextUtil.getApplicationContext();
        Map<String, EventComponent> eventBeanMap = context.getBeansOfType(EventComponent.class);
        eventBeanMap.values().forEach(event -> {
            if (event.getClass().isAnnotationPresent(EventAnnonation.class)) {
                EventAnnonation eventAnnonation = event.getClass().getAnnotation(EventAnnonation.class);
                eventComponentMap.put(eventAnnonation.event(), event);
            }
        });
    }

    @Override
    public void invokeEvent(TransNeedEventEnum eventEnum, Context context) {
        if (eventComponentMap.containsKey(eventEnum)) {
            eventComponentMap.get(eventEnum).invokeEvent(context);
        }
    }
}





4.2.3 狀態機上下文

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Context {
    private String userName;
    private Long transNeedId;
    private TransNeed transNeed;
}





4.2.4 狀態枚舉

public enum TransNeedStatusEnum {
    /**
     * 開始狀態
     */
    None,
    /**
     * 待分配陸運服務商
     */
    UN_ASSIGN_CARRIER,
    /**
     * 待分配車輛和司機
     */
    UN_ASSIGN_CAR,
    /**
     * 訂單已處理,已安排司機提貨
     */
    ASSIGNED_CAR,
    /**
     * 已完成提貨
     */
    PICKUPED,
    /**
     * 運輸中
     */
    IN_TRANSIT,
    /**
     * 已通過內地海關
     */
    PASS_CUSTOMS,
    /**
     * 您的貨物部分妥投部分投遞失敗
     */
    PARTIAL_DELIVERIED,
    /**
     * 您的貨物妥投
     */
    ALL_DELIVERIED,
    /**
     * 您的貨物被拒收
     */
    ALL_REJECTED,
    /**
     * 委托訂單被取消
     */
    CANCELED,
    /**
     * 單據結束歸檔
     */
    FINISH;

}





4.2.5 事件枚舉

public enum TransNeedEventEnum {
        // 系統事件
        Create_Event,
        Normal_Update_Event,
        /**
         * 分配服務商事件
         */
        Assign_Carrier_Event,
        /**
         * 派車事件
         */
        Assign_Car_Event,

        // 車輛任務(trans_jbo)執行修改調度單(trans_task)狀態的事件
        Trans_Job_Status_Change_Event,

        // 派送事件
        Partial_Delivery_Event,
        All_Delivery_Event,
        Partial_Reject_Event,
        All_Reject_Event,

        // 調度單中的任務單取消事件
        Order_Cancel_Event,

        //  單據結束
        Order_Finish;

        public boolean isSystemEvent() {
                return this == Create_Event ||
                        this == Normal_Update_Event;
        }
}






4.2.6 介面Demo

4.2.6.1 創建需求單
/**
 *  接單
* @return
 */
@RequestMapping("/start/{fsNo}/{remark}")
public Context start(@PathVariable("fsNo") String fsNo, @PathVariable("remark") String remark) {
    Context context = contextService.getContext();
    Object newStatus = stateMachine.getStateMachine().fireEvent(TransNeedStatusEnum.None, TransNeedEventEnum.Create_Event, context);
    TransNeed transNeed = transNeedService.createTransNeed(fsNo, remark, newStatus.toString());
    context.setTransNeed(transNeed);
    context.setTransNeedId(transNeed.getId());
    return context;
}





4.2.6.2 分配服務商
/**
 * 運輸規劃單生成調度單,調度單綁定服務商
*/
@RequestMapping("/assignCarrier/{id}")
public Context assignCarrier(@PathVariable("id") Long id) {
    Context context = contextService.getContext(id);
    TransNeedStatusEnum prevStatus = TransNeedStatusEnum.valueOf(context.getTransNeed().getStatus());
    stateMachine.getStateMachine().fireEvent(prevStatus, TransNeedEventEnum.Assign_Carrier_Event, context);
    return context;
}





4.2.6.3 分配車輛
@RequestMapping("/assignCar/{id}")
public Context assignCar(@PathVariable("id") Long id) {
    Context context = contextService.getContext(id);
    TransNeedStatusEnum prevStatus = TransNeedStatusEnum.valueOf(context.getTransNeed().getStatus());
    log.info("trans need id: {}, prev status: {}", id, prevStatus);
    stateMachine.getStateMachine().fireEvent(prevStatus, TransNeedEventEnum.Assign_Car_Event, context);
    return context;
}





5 狀態機對比

維度\組件 Spring StateMachine COLA StateMachine
API調用 使用Reactive的Mono、Flux方式進行API調用 同步的API調用,如果有需要也可以將方法通過MQ、定時任務、線程池做成非同步的
代碼量 core包284個介面和類 36個介面和類
生態 非常豐富
定製化難度 困難 簡單
代碼更新狀態 將近1年沒有更新 半年前

綜上,如果是直接使用狀態機的組件庫,可以考慮使用Spring的狀態機,如果是要漸進式的使用狀態機,逐步按照自己的需求去定製化狀態機以滿足業務需求,建議使用COLA的狀態機。

6 iTMS使用狀態機的計劃

iTMS準備漸進式的使用COLA的狀態機組件,先輕量級使用狀態機進行運輸相關域的狀態變更,後續按照DDD的狀態和事件的分析,使用CQRS的設計模式對命令做封裝,調用狀態機進行業務流轉。

作者:京東物流 馬瑞

來源:京東雲開發者社區 自猿其說Tech


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

-Advertisement-
Play Games
更多相關文章
  • ### 你可以按照以下步驟製作自己的Python模塊: 1.創建一個新的.py文件,並定義你自己的函數或類。 2.編寫文檔字元串docstring,說明該函數或類的作用、參數和返回值說明等。 3.給你的函數或類添加恰當的註釋。 4.將該.py文件放在工程目錄的一個新文件夾中,這個文件夾就是你的模塊。 ...
  • ## 概述 Mybatis 的核心組件如下所示: - Configuration:用於描述 MyBatis 的主配置信息,其他組件需要獲取配置信息時,直接通過 Configuration 對象獲取。除此之外,MyBatis 在應用啟動時,將 Mapper 配置信息、類型別名、TypeHandler ...
  • 大家好,我是棧長。 經過 Spring Cloud Alibaba 2022 的第一個候選版本 2022.0.0.0-RC1 發佈 7 個多月後,中間還有一個 2022.0.0.0-RC2 版本,就在前幾天,**Spring Cloud Alibaba 2022.0.0.0 正式版** 終於正式發佈 ...
  • 在 Protocol Buffers (protobuf) 中,可以使用特定的選項來指定生成的 JSON 標簽。通過在消息定義中使用 `[(json_name)]` 選項,可以控制生成的 JSON 欄位名稱。這樣可以確保 Protocol Buffers 和 JSON 之間的互操作性。 下麵是一個示 ...
  • ## 一、問題是怎麼發現的 最近有個新系統開發完成後要上線,由於系統調用量很大,所以先對核心介面進行了一次壓力測試,由於核心介面中基本上只有純記憶體運算,所以預估核心介面的壓測QPS能夠達到上千。 壓測容器配置:4C8G 先從10個併發開始進行發壓,結果cpu一下就飆升到了100%,但是核心介面的qp ...
  • 編程基礎常識 一、註釋 1、對代碼的說明與解釋,它不會被編譯執行,也不會顯示在編譯結果中 2、註釋分為:單行註釋和多行註釋 3、用#號開始,例如:#這是我的第一個python程式 4、註釋可以寫在單獨一行,也可以寫在一句代碼後面 5、不想執行編譯,又不能刪除的代碼,可以先用#註釋掉,代碼批量註釋用C ...
  • 要解決多線程併發問題,常見的手段無非就幾種。加鎖,如使用synchronized,ReentrantLock,加鎖可以限制資源只能被一個線程訪問;CAS機制,如AtomicInterger,AtomicBoolean等原子類,通過自旋的方式來嘗試修改資源;還有本次我們要介紹的ThreadLocal類 ...
  • MQ(Message Queue)作為一種用於實現非同步通信的技術,具有重要的作用和應用場景。在面試過程中,MQ相關的問題經常被問到,因此瞭解MQ的用途和設計原則是必不可少的。本文總結了MQ的常見面試題,包括MQ的作用、產品選型、消息不丟失的保證、消息消費的冪等性、消息順序的保證、消息的高效讀寫、分佈... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...