状态机的介绍和使用

状态机,介绍,使用 · 浏览次数 : 104

小编点评

**代码分析** * **Context** 类用于管理各种上下文信息,包括用户名、交易需求ID和状态变更。 * ****TransNeed** 枚举表示各种状态变更,例如创建需求、分配服务商、分配车辆等。 * ****stateMachine** 状态机用于管理各种请求和状态变更。 * ****Mono** 和 ****Flux** 模态用于进行异步操作,例如获取状态变更、分配服务商等。 * ****CQRS** 设计模式用于将命令封装到状态机中,并在业务流转中进行调用。 **主要功能** * 创建需求单 * 分配服务商 * 分配车辆 * 状态机对比维度 *渐进式使用COLA 状态机 **主要技术** * Spring 状态机 * CQRS * Reactive * MQ *定时任务 *线程池 **核心代码** * **Context** 类中的 start 方法创建新的上下文对象。 * **TransNeed** 枚举中的 Create_Event 表示创建需求单。 * **stateMachine** 状态机的 getStateMachine()方法获取当前状态的变更。 * **Mono** 和 **Flux** 模态的进行事件处理。 * **CQRS** 设计模式中的命令封装到 **TransNeed** 枚举中。

正文

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

与状态机的介绍和使用相似的内容:

状态机的介绍和使用

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。

介绍vue3的钩子函数activated和deactivated使用场景

activated和deactivated是Vue3中的两个生命周期钩子函数。 activated钩子函数在组件被激活时调用,通常用于恢复组件的状态或执行一些初始化操作。例如,如果一个组件被从路由中激活,你可能需要在该组件被激活时从本地存储中加载一些数据。 下面是一个示例代码:

超详细的 Jenkins 安全tips

Jenkins 作为一个开放的、可定制的平台,即使在默认状态下也提供了不错的安全性。但是鉴于 Jenkins 连接了许多行业工具,因此也存在一定安全隐患。本篇文章将会介绍一些方法和工具,来确保 Jenkins 实例的安全和可靠,同时也给使用 Jenkins 的用户提供安全保障。 保持更新 软件漏洞随

[转帖]TiDB Control 使用说明

https://docs.pingcap.com/zh/tidb/stable/tidb-control TiDB Control 是 TiDB 的命令行工具,用于获取 TiDB 状态信息,多用于调试。本文介绍了 TiDB Control 的主要功能和各个功能的使用方法。 注意 TiDB Contr

面向状态机编程:复杂业务逻辑应对之道

在研发项目中,经常能遇到复杂的状态流转类的业务场景,比如游戏编程中NPC的跳跃、前进、转向等状态变化,电商领域订单的状态变化等。这类情况其实可以有一种优雅的实现方法:状态机。本文重点介绍有限状态机,并结合具体项目,通过状态机的应用将状态和业务逻辑解耦,便于简化复杂业务逻辑,降低理解成本。另外,重点讲解如何优雅的解决更广泛的复杂业务问题。

循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(6) -- 窗口控件脏数据状态IsDirty的跟踪处理

在我们窗口新增、编辑状态下的时候,我们往往会根据是否修改过的痕迹-也就是脏数据状态进行跟踪,如果用户发生了数据修改,我们在用户退出窗口的时候,提供用户是否丢弃修改还是继续编辑,这样在一些重要录入时的时候,可以避免用户不小心关掉窗口,导致窗口的数据要重新录入的尴尬场景。本篇随笔介绍基于WPF开发中,窗...

解密TCP连接断开:四次挥手的奥秘和数据传输的安全

本文将介绍TCP连接的断开过程,重点关注四次挥手的过程和状态变迁,以及为什么挥手需要四次和为什么需要TIME_WAIT状态。在TCP连接断开的过程中,双方需要发送FIN和ACK报文来确保数据的可靠传输和连接的正确关闭。挥手需要四次的原因是为了确保数据的完整传输和连接的可靠关闭。

[转帖]【测试】 FIO:ceph/磁盘IO测试工具 fio(iodepth深度)

目录 随看随用 NAS文件系统测试 块系统测试 FIO用法 FIO介绍 FIO 工具常用参数: FIO结果说明 I/O 的重放('录'下实际工况的IO,用fio'重放') fio工作参数可以写入配置文件 fio的iodepth参数说明 IO状态监控: Iostat介绍 其他相关工具和内容 与FIO相

数据监控预警系统,实现不同端信息推送

数据是反映产品和用户状态最真实的一种方式,通过数据指导运营决策,驱动业务增长。数据可分为2种情况:数据监控和数据分析;Wyn嵌入式商业智能软件就提供了完整的数据监控和数据分析能力,下面就为大家进行一个详细介绍。 1.什么是数据监控? 数据监控是及时有效的反馈出数据异常的一种手段,通过对数据的监控去观

深入理解操作系统中进程与线程的区别及切换机制(下)

本文首先介绍了进程的控制结构,即进程控制块(PCB),它是表示进程的数据结构,包含了进程的相关信息和资源。PCB之间通过链表连接,形成就绪队列和阻塞队列,用于进程调度和资源管理。接着,文章详细探讨了进程的切换过程。进程切换是为了保证公平分配CPU时间片,涉及保存和恢复进程的执行上下文、更新进程状态和调度算法选择等步骤。文中还提到了进程上下文切换的场景,如时间片用完、内存不足、高优先级进程需求等。最