开发者社区 > 博文 > 状态机的介绍和使用
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

状态机的介绍和使用

  • 18****
  • 2023-07-24
  • IP归属:北京
  • 1152浏览

    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 StateMachineCOLA StateMachine
    API调用使用Reactive的Mono、Flux方式进行API调用同步的API调用,如果有需要也可以将方法通过MQ、定时任务、线程池做成异步的
    代码量core包284个接口和类36个接口和类
    生态非常丰富
    定制化难度困难简单
    代码更新状态将近1年没有更新半年前

    综上,如果是直接使用状态机的组件库,可以考虑使用Spring的状态机,如果是要渐进式的使用状态机,逐步按照自己的需求去定制化状态机以满足业务需求,建议使用COLA的状态机。

    6 iTMS使用状态机的计划

    iTMS准备渐进式的使用COLA的状态机组件,先轻量级使用状态机进行运输相关域的状态变更,后续按照DDD的状态和事件的分析,使用CQRS的设计模式对命令做封装,调用状态机进行业务流转。


    文章数
    1
    阅读量
    1152

    作者其他文章