2020-07-08

Spring Boot 2.x actual state machine

Spring StateMachine is a state machine framework. In the Spring framework project, developers can obtain a business state machine through simple configuration without having to manage the definition and initialization of the state machine. In this article today, we use a case to learn the usage of Spring StateMachine framework.

Case introduction

Suppose there is such an object in a business system. It has three states: draft, pending release, and release completed. The business actions for these three states are relatively simple, namely: go online, release, and rollback. The business state machine is shown below.


Actual combat

Next, based on the above business state machine, a Spring StateMachine demonstration.

Create a basic Spring Boot project, add the Spring StateMachine dependency in the main pom file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>online.javaadu</groupId>
  <artifactId>statemachinedemo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>statemachinedemo</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <!--spring statemachine-->
        <dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-core</artifactId>
        <version>2.1.3.RELEASE</version>
      </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
Define the state enumeration and event enumeration, the code is as follows:


public enum States {
    DRAFT,
    PUBLISH_TODO,
    PUBLISH_DONE,
}


public enum Events {
    ONLINE,
    PUBLISH,
    ROLLBACK
}
Complete the configuration of the state machine, including: (1) the initial state and all states of the state machine; (2) transition rules between states
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
        states.withStates().initial(States.DRAFT).states(EnumSet.allOf(States.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
        transitions.withExternal()
            .source(States.DRAFT).target(States.PUBLISH_TODO)
            .event(Events.ONLINE)
            .and()
            .withExternal()
            .source(States.PUBLISH_TODO).target(States.PUBLISH_DONE)
            .event(Events.PUBLISH)
            .and()
            .withExternal()
            .source(States.PUBLISH_DONE).target(States.DRAFT)
            .event(Events.ROLLBACK);
    }
}
Define a test business object, the state transition of the state machine will be reflected in the state change of the business object
@WithStateMachine
@Data
@Slf4j
public class BizBean {

    /**
     * @see States
     */
    private String status = States.DRAFT.name();

    @OnTransition(target = "PUBLISH_TODO")
    public void online() {
        log.info("target status:{}", States.PUBLISH_TODO.name());
        setStatus(States.PUBLISH_TODO.name());
    }

    @OnTransition(target = "PUBLISH_DONE")
    public void publish() {
        log.info("target status:{}", States.PUBLISH_DONE.name());
        setStatus(States.PUBLISH_DONE.name());
    }

    @OnTransition(target = "DRAFT")
    public void rollback() {
        log.info("target status:{}", States.DRAFT.name());
        setStatus(States.DRAFT.name());
    }

}
Write test cases, here we use the CommandLineRunner interface instead, define a StartupRunner, start the state machine in the run method of this class, send different events, and verify the flow of the state machine through the log.
public class StartupRunner implements CommandLineRunner {

    @Resource
    StateMachine<States, Events> stateMachine;

    @Override
    public void run(String... args) throws Exception {
        stateMachine.start();
        stateMachine.sendEvent(Events.ONLINE);
        stateMachine.sendEvent(Events.PUBLISH);
        stateMachine.sendEvent(Events.ROLLBACK);
    }
}
After running the above program, we can get the following output in the console. We performed three operations: going online, publishing, and rolling back. In the following figure, we did see the corresponding logs. But I also found that there is an unexpected place-when starting the state machine, a log was also printed-"Operation rolls back, back to draft state. target status: DRAFT", here should be the state machine settings Triggered in the initial state.

Analysis

As shown in the actual combat process above, the steps to use Spring StateMachine are as follows:


  • Define state enumeration and event enumeration
  • Define the initial state and all states of the state machine
  • Define transition rules between states

Use state machines in business objects to write listener methods that respond to state changes
In order to manage the state change operations in a unified manner, we will consider introducing a state machine in the project, so that other business modules are isolated from the state transition module, and other business modules will not be entangled in what the current state is. What should be done. When applying the state machine to realize the business requirements, the key is the analysis of the business state. As long as the state machine is designed without problems, the specific implementation can choose to use Spring State Machine, or you can implement a state machine yourself.

The advantage of using Spring StateMachine is that you don't need to care about the implementation details of the state machine, you only need to care about the state of the business, the transition rules between them, and the real business operations that need to be performed after each state transition.

No comments:

Post a Comment