Introduction and use of state machine | Jingdong logistics technical team

1 Introduction to state machine

1.1 Definition

Let's first give the basic definition of a state machine. In a word:

State machine is the abbreviation of finite state automaton, which is a mathematical model abstracted from the operation rules of real things.

Let's first explain what is "state" (State). Real things have different states. For example, an automatic door has two states: open and closed. What we usually call a state machine is a finite state machine, that is, the number of states of the described thing is limited. For example, the state of an automatic door is two open and closed.

State machine, that is, State Machine, does not refer to an actual machine, but refers to a mathematical model . To put it bluntly, it generally refers to a state transition diagram. For example, according to the operating rules of automatic doors, we can abstract the following graph.

The automatic door has two states, open and closed. In the closed state, if the door open signal is read, the state will switch to open. If the door close signal is read in the open state, the state will switch to closed.

The full name of state machine is finite state automata, and the word automatic also contains important meanings. Given a state machine, given its current state and input, the output state can be calculated explicitly . For example, for an automatic door, given the initial state closed and given the input "open the door", then the next state can be calculated.

In this way, we have introduced the basic definition of the state machine. Repeat: state machine is the abbreviation of finite state automata , and it is a mathematical model abstracted from the operation rules of real things.

1.2 Four concepts

The four concepts of the state machine are given below.

The first one is State, state. A state machine must contain at least two states. For example, in the example of the automatic door above, there are two states: open and closed.

The second is Event, event. An event is a trigger condition or password to perform an operation. For automatic doors, "press the door open button" is an event.

The third is Action, action. Actions to be performed after an event occurs. For example, the event is "press the button to open the door", and the action is "open the door". When programming, an Action generally corresponds to a function.

The fourth is Transition, transformation. That is, changing from one state to another. For example, "door opening process" is a transformation.

2 DSL

2.1 DSL

A DSL is a tool whose core value is that it provides a means to more clearly communicate the intent of some part of the system.

This clarity is not just an aesthetic pursuit. The easier a piece of code is to understand, the easier it is to find bugs and make changes to the system. Therefore, we encourage meaningful variable names, clear documentation, and clear code structure. For the same reason, we should also encourage the adoption of DSLs.

By definition, a DSL is a computer programming language with limited expressiveness for a specific domain.

This definition contains 3 key elements:

Language nature : A DSL is a programming language, so it must be able to express coherently—whether it is a single expression or a combination of multiple expressions.

Limited expressiveness (limited expressiveness) : General-purpose programming languages ​​provide a wide range of capabilities: support a variety of data, control, and abstract structures. These abilities are useful, but they can also make languages ​​difficult to learn and use. A DSL supports only the minimal set of features required for a particular domain. Using DSL, it is impossible to build a complete system, on the contrary, it can solve the problem of a certain aspect of the system.

Domain focus : A language of this limited capability is only useful in a well-defined small domain. This field is what makes the language worth using.

For example, the regular expression, /\d{3}-\d{3}-\d{4}/ is a typical DSL, which solves the problem of string matching in this specific field.

2.2 Classification of DSL

According to the type, DSL can be divided into three categories: internal DSL (Internal DSL), external DSL (External DSL), and language workbench (Language Workbench).

Internal DSL is a specific usage of a general language. A script written in the internal DSL is a legitimate program, but it has a certain style and only uses a subset of the language's features to deal with a small aspect of the overall system. Programs written in this DSL have the flavor of a custom language, distinct from the host language in which it is used. For example, our state machine is Internal DSL, which does not support script configuration, and it is still in Java language when used, but it does not prevent it from being also a DSL.

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




External DSL is a language "different from the main language used by the application system". External DSLs usually use a custom syntax, although it's common to choose a syntax in another language (XML is a common choice). Examples include XML configuration files used by systems like Struts and Hibernate.

Workbench is a dedicated IDE. To put it simply, the workbench is the productization and visualization form of DSL.

The three categories of DSLs have a progressive relationship from the front to the back. Internal DSL is the simplest and the implementation cost is low, but it does not support "external configuration". Workbench not only realizes configuration, but also realizes visualization, but the implementation cost is also the highest. Their relationship is shown in the figure below:

2.3 DSL example

2.3.1 Internal DSL example

HTML: written in natural language

In Groovy, XML can be generated in a human-readable way through DSL

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()




will eventually generate

<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>




Description of the role of 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>




Compared with dynamic languages ​​such as Java, the most different thing here is that the non-existent method of xml.html can be compiled and run. It rewrites the invokeMethod method internally and performs closure traversal, saving a lot of POJO objects. higher efficiency.

2.3.2 External DSL

Taking plantUML as an example, the external DSL is not limited to the grammar of the host language and is very user-friendly, especially for users who do not understand the grammar of the host language. But the custom syntax of the external DSL needs a matching syntax analyzer. Common grammatical analyzers are: YACC, ANTLR, etc.

https://github.com/plantuml/plantuml

https://plantuml.com/zh/

2.3.3 DSL & DDD (Domain Driven)

The integration of DDD and DSL has three points: domain-oriented, model assembly method, and layered architecture evolution. DSL can be seen as a layer of shell on top of the domain model, which can significantly enhance the capabilities of the domain model.

It has two main values, one is to improve the productivity of developers, and the other is to improve the communication between developers and domain experts. An external DSL is a way of assembling the domain model.

3 Research on state machine implementation

3.1 Spring Statemachine

Official website: https://spring.io/projects/spring-statemachine#learn

Source code: 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 provides the following features:

•Easy to use flat one level state machine for simple use cases. (Easy to use flat one level state machine for simple use cases.)

•Hierarchical state machine structure to ease complex state configuration. (Hierarchical state machine structure to ease complex state configuration.)

•State machine regions to provide even more complex state configurations. (The state machine region provides more complex state configurations.)

•Usage of triggers, transitions, guards and actions. (Usage of triggers, transitions, guards and actions.)

•Type safe configuration adapter. (Apply safe configuration adapter.)

•Builder pattern for easy instantiation for use outside of Spring Application context (a generator pattern for simple instantiation used outside the Spring Application context)

•Recipes for usual use cases (manual for usual use cases)

•Distributed state machine based on a Zookeeper State machine event listeners. (Distributed state machine state machine event listener based on Zookeeper.)

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

•Store machine config in a persistent storage. (storage state machine configuration to the persistence layer)

• Spring IOC integration to associate beans with a state machine. (Spring IOC integration associates beans with a state machine)

Spring StateMachine provides the Eclipse Plugin of papyrus to assist in building state machines.

More Eclipse modeling plug-ins can be found in the documentation: https://docs.spring.io/spring-statemachine/docs/3.2.0/reference/#sm-papyrus

For the configuration, definition, event, state extension, context integration, security, error handling, etc. of the Spring state machine, you can refer to the following documents:

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

3.2 COLA state machine DSL implementation

COLA is the abbreviation of Clean Object-Oriented and Layered Architecture, which stands for "Clean Object-Oriented and Layered Architecture". Currently COLA has been developed to COLA v4. COLA provides a DDD landing solution, which includes an open source, simple, lightweight, and high-performance state machine DSL implementation to solve the state flow problem in the business.

The COLA state machine component implements a state machine that only supports simple state flow. The core concept of the state machine is shown in the figure below, mainly including:

1. State: state

2.Event: event, the state is triggered by the event, causing changes

3.Transition: flow, means from one state to another state

4.External Transition: external flow, flow between two different states

5.Internal Transition: internal flow, flow between the same state

6.Condition: Condition, indicating whether to allow to reach a certain state

7. Action: Action, what can be done after reaching a certain state

8. StateMachine: state machine

The core semantic model of the entire state machine (Semantic Model):

4 State machine DEMO

4.1 Spring state machine example

Code address: http://xingyun.jd.com/codingRoot/ilt/spring-statemachine-demo/

For example, the start node is SI, the end node is SF, and the start node is followed by a simple state machine of three nodes S1, S2, and S3.

The Spring Boot project needs to introduce Spring state machine components.

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




4.1.1 Constructing a state machine

@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 State machine state listener

@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 State machine configuration

@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 Interface example

4.1.4.1 Get state machine state list
@RequestMapping("info")
public String info() {
    return StringUtils.collectionToDelimitedString(
            stateMachine.getStates()
                    .stream()
                    .map(State::getId)
                    .collect(Collectors.toList()),
                    ",");
}




4.1.4.2 State machine start

Before performing event operations on the Spring state machine, the state machine must be enabled first

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




4.1.4.3 Event Actions
@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 Get the current state of the state machine
@GetMapping("state")
public String state() {
    return Mono.defer(() -> Mono.justOrEmpty(stateMachine.getState().getId())).block();
}




4.1.4.5 Console output of a state transition
: 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




It can be seen that the state goes from none to the SI start node, then to S1, S2, and then S2 passes through the E2 event to the SF end node.

4.2 Example of COLA state machine

Code address: http://xingyun.jd.com/codingRoot/ilt/ilt-component-statemachine/

For example: the status of the transportation demand list in iTMS currently includes: to be allocated, allocated, in transit, partially delivered, all delivered, all rejected, and canceled.

4.2.1 Constructing a state machine

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");




From the code, you can easily expand the state and corresponding events, and the state machine automatically performs the flow of business states. The generated state flow diagram is as follows:

@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 State machine event processing

/**
 * 一种是通过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 and EventBus simple demo example:

/**
 * @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 State machine context

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




4.2.4 Status Enumeration

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 Event Enumeration

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 Interface Demo

4.2.6.1 Create Requisition Order
/**
 *  接单
* @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 Allocation of service providers
/**
 * 运输规划单生成调度单,调度单绑定服务商
*/
@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 Allocation of vehicles
@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 state machine comparison

Dimension\Component Spring StateMachine COLA StateMachine
API calls Use Reactive's Mono and Flux methods to make API calls Synchronous API calls, if necessary, the method can also be made asynchronous through MQ, timed tasks, and thread pools
amount of code core package 284 interfaces and classes 36 interfaces and classes
ecology Very rich none
customization difficulty difficulty Simple
code update status It hasn't been updated for almost 1 year half year ago

To sum up, if you directly use the component library of the state machine, you can consider using the Spring state machine. If you want to use the state machine gradually, and gradually customize the state machine according to your own needs to meet the business needs, it is recommended to use COLA. state machine.

6 iTMS plan using state machine

iTMS prepares to gradually use the state machine component of COLA. First, the light-weight state machine is used to change the state of the transportation-related domain . Then, according to the analysis of the state and events of DDD, the CQRS design pattern is used to encapsulate the command and call the state machine. Carry out business flow.

Author: JD Logistics Ma Rui

Source: JD Cloud developer community Ziqishuo Tech

Clarification about MyBatis-Flex plagiarizing MyBatis-Plus Arc browser officially released 1.0, claiming to be a substitute for Chrome OpenAI officially launched Android version ChatGPT VS Code optimized name obfuscation compression, reduced built-in JS by 20%! LK-99: The first room temperature and pressure superconductor? Musk "purchased for zero yuan" and robbed the @x Twitter account. The Python Steering Committee plans to accept the PEP 703 proposal, making the global interpreter lock optional . The number of visits to the system's open source and free packet capture software Stack Overflow has dropped significantly, and Musk said it has been replaced by LLM
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10092135
Recommended