Simple implementation of Spring state machine

1. What is a state machine?

State machine, also known as finite state automaton, is a computing model that represents a limited number of states and behaviors such as transitions and actions between these states. The concept of state machine can actually be applied to various fields, including electronic engineering, linguistics, philosophy, biology, mathematics and logic, etc. For example, elevators, fans, door gates, etc. in daily life involve multiple states. , as the action is executed, the state will be transferred. In the field of software programming, using the state machine idea can also simplify our design process and increase the readability and maintainability of the code.

Currently, there are many open source state machines in use such as: Spring StateMachine, Squirrel StateMachine and Alibaba’s lightweight Cola-StateMachine. This article mainly introduces the use of Spring’s state machine.

2. Core concepts of Spring state machine

  • transition: Transition is the relationship between the original state and the target state, used to transfer the state machine from one state to another state
  • source: the current status of the node
  • target: The target state of the node
  • event: Trigger the action of a node from the current state to the target state, such as from State A to State B
  • guard: Also called “guard”, when an event request is triggered, verification rules can be defined to verify whether subsequent actions can be executed.
  • action: used to implement the business logic corresponding to the current node (the system's response after the event occurs)
  • withChoice: When executing an action that may lead to multiple results, you can choose to use choice+guard to jump.
  • withInternal: We support three different types of conversions, external, internal and local. Conversions are triggered by a signal, which is an event or timer sent to the state machine

3. Spring state machine usage example

Spring StateMachine is the state machine implementation officially provided by Spring. Its core components are as follows:

  • StateMachine: State machine instance, which can trigger events, perform state transitions, and obtain current status and other information;
  • StateMachineStateConfigurer: used to configure the state, define various states in the state machine, and specify the behavior and attributes of each state;
  • StateMachineTransitionConfigurer: used to configure the transition relationship between states, defining the triggering event, source state, target state of the transition, as well as the conditions and actions of the transition;
  • StateMachineConfigurationConfigurer: State machine system configuration, used to configure the global properties and behaviors of the state machine, including the execution mode, concurrency strategy, listener, etc. of the state machine;
  • StateMachineListenerAdapter: event listener, used to simplify the implementation of state machine event listener (StateMachineEventListener)
  1. Environment preparation:
<dependency>
     <groupId>org.springframework.statemachine</groupId>
     <artifactId>spring-statemachine-starter</artifactId>
     <version>2.5.0</version>
 </dependency> 
  1. Define reward status and event enumeration classes
 public enum AwardState {
    INACTIVE, 
    ACTIVE, 
    PAUSE, 
    FINISH
}

public enum AwardEvent {
    AUTO_ACTIVATE, 
    AUTO_FINISH, 
    FINISH, 
    PAUSE, 
    RESUME
} 
  1. State machine configuration class:
@Configuration
@EnableStateMachine
public class AwardStateMachineConfig extends EnumStateMachineConfigurerAdapter<AwardState, AwardEvent> {
    @Autowired
    private AwardStateMachineListener awardStateMachineListener;

    @Override
    public void configure(StateMachineStateConfigurer<AwardState, AwardEvent> states) throws Exception {
        states
        .withStates()
        .initial(AwardState.INACTIVE)
        .states(EnumSet.allOf(AwardState.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<AwardState, AwardEvent> transitions) throws Exception {
        transitions
                .withExternal()
                    .source(AwardState.INACTIVE)
                    .target(AwardState.ACTIVE)
                .event(AwardEvent.AUTO_ACTIVATE)
                .and()
                .withExternal()
                    .source(AwardState.INACTIVE)
                    .target(AwardState.FINISH)
                    .event(AwardEvent.AUTO_FINISH)
                .and()
                .withExternal()
                    .source(AwardState.ACTIVE)
                    .target(AwardState.FINISH)
                    .event(AwardEvent.FINISH)
                .and()
                .withExternal()
                    .source(AwardState.ACTIVE)
                    .target(AwardState.PAUSE)
                    .event(AwardEvent.PAUSE)
                .and()
                .withExternal()
                    .source(AwardState.PAUSE)
                    .target(AwardState.ACTIVE)
                    .event(AwardEvent.RESUME)
                .and()
                .withExternal()
                    .source(AwardState.PAUSE)
                    .target(AwardState.FINISH)
                    .event(AwardEvent.FINISH);
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<AwardState, AwardEvent> config) throws Exception {
        config.withConfiguration().autoStartup(true).listener(awardStateMachineListener);
    }
} 
  1. State machine listener:
@Component
public class AwardStateMachineListener extends StateMachineListenerAdapter<AwardState, AwardEvent> {
    @Override
    public void transition(Transition<AwardState, AwardEvent> transition) {
        System.out.println("state transfer from " + transition.getSource().getId() + " to " + transition.getTarget().getId());
    }

    /*@Override
    public void stateChanged(State<AwardState, AwardEvent> from, State<AwardState, AwardEvent> to) {
        System.out.println("status change from " + from.getId() + " to " + to.getId());
    }*/
} 
  1. Reward services
@Service
public class AwardStateMachineService {

    @Resource
    private StateMachine<AwardState, AwardEvent> stateMachine;

    public void autoActivate() {
        stateMachine.sendEvent(AwardEvent.AUTO_ACTIVATE);
    }

    public void autoFinish() {
        stateMachine.sendEvent(AwardEvent.AUTO_FINISH);
    }

    public void finish() {
        stateMachine.sendEvent(AwardEvent.FINISH);
    }

    public void pause() {
        stateMachine.sendEvent(AwardEvent.PAUSE);
    }

    public void resume() {
        stateMachine.sendEvent(AwardEvent.RESUME);
    }

} 
  1. Test:
@SpringBootTest
class AwardStatemachineApplicationTests {

    @Autowired
    private AwardStateMachineService awardStateMachineService;

    @Autowired
    private StateMachine<AwardState, AwardEvent> stateMachine;

    @Test
    void awardStageTest() {
        // Send an event to automatically activate the lottery
        awardStateMachineService.autoActivate();
        // Check if the status changes toACTIVE
        assert (stateMachine.getState().getId() == AwardState.ACTIVE);

        // Send event to pause the lottery
        awardStateMachineService.pause();
        // Check if the status changes toPAUSE
        assert (stateMachine.getState().getId() == AwardState.PAUSE);

        // Send event to resume lottery
        awardStateMachineService.resume();
        // Check if the status changes toACTIVE
        assert (stateMachine.getState().getId() == AwardState.ACTIVE);

        // Send event to end lottery
        awardStateMachineService.finish();
        // Check if the status changes toFINISH
        assert (stateMachine.getState().getId() == AwardState.FINISH);
    }

} 

Summary: This article mainly introduces some basic concepts of Spring state machine, as well as the use of state flow, and some advanced uses of Spring state machine, such as state persistence, state parallelism (parallel, fork, join), Sub-state machines, etc. will be updated later in the article.

reference:
https://docs.spring.io/spring-statemachine/docs/2.0.2.RELEASE/reference/htmlsingle/#glossary