SpringBoot State machine
The concept of a state machine is most likely older than any reader of this reference documentation and definitely older than the Java language itself.
Usage Scenarios
- You can represent the application or part of its structure as states.
- You want to split complex logic into smaller manageable tasks.
- Application is already suffering concurrency issues with something happening asynchronously.
You are already trying to implement a state machine when you:
- Use boolean flags or enums to model situations.
- Have variables that have meaning only for some part of your application lifecycle.
- Loop through an if-else structure (or, worse, multiple such structures), check whether a particular flag or enum is set, and then make further exceptions about what to do when certain combinations of your flags and enums exist or do not exist.
Spring Statemachine (SSM) is a framework that lets application developers use traditional state machine concepts with Spring applications. SSM provides the following features:
- 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.
- Type-safe configuration adapter.
- State machine event listeners.
- Spring IoC integration to associate beans with a state machine
System Requirement
- JDK 8 (all artifacts have JDK 7 compatibility)
- Spring Framework 5.3.19.
Why State machines are powerful ?
- Behaviour is always guaranteed to be consistent.
- Operational rules are written in stone when the machine is started.
- Application may exist in a finite number of states and certain predefined triggers can take your application from one state to the next. Such triggers can be based on either events or timers.
Terminology
- States:- The specific state of the state machine. Finite and predetermined values.Frequently defined in an enumeration.
- Events:- Something that happens to the system — may or may not change the state.Frequently defined in an enumerat
- Actions:- The response of the State Machine to events. Can be changing variables, calling a method or changing to a different state.
- Transitions:- Type of action which changes state
- Guards:- Boolean conditions
- Extended State:- State Machine variables (in addition to state)
How this works ?
- State machine configuration , states and transitions is written only once in one config
- whenver a changes to entity is needed : Build the statemachine from current entity state
- Fire events to state machine
- Intercept the change and save entity according to statemachine current state
To get started, we need to add the main Maven dependency:
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>3.2.0.RELEASE</version> </dependency>
A typical State machine for the attached image looks the snnipet . Note the simple state , Fork states and Join States transtions.
@Configuration @EnableStateMachineFactory public class StateMachineConfig extends StateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withConfiguration() .autoStartup(true) .taskExecutor(new SyncTaskExecutor()) .taskScheduler(new ConcurrentTaskScheduler()) .listener(adapter); } @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.ADDED) .state(States.IN_CHECK) .fork(States.IN_CHECK) .join(States.ALL_CHECKS_FINISHED) .state(States.APPROVED) .end(States.ACTIVE) .and() .withStates() .parent(States.IN_CHECK) .initial(States.SECURITY_CHECK_STARTED) .end(States.SECURITY_CHECK_FINISHED) .and() .withStates() .parent(States.IN_CHECK) .initial(States.WORK_PERMIT_CHECK_STARTED) .state(States.WORK_PERMIT_CHECK_PENDING_VERIFICATION) .end(States.WORK_PERMIT_CHECK_FINISHED); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.ADDED) .target(States.IN_CHECK) .event(Events.BEGIN_CHECK) .and() .withExternal() .source(States.SECURITY_CHECK_STARTED) .target(States.SECURITY_CHECK_FINISHED) .event(Events.FINISH_SECURITY_CHECK) .and() .withExternal() .source(States.WORK_PERMIT_CHECK_STARTED) .target(States.WORK_PERMIT_CHECK_PENDING_VERIFICATION) .event(Events.COMPLETE_INITIAL_WORK_PERMIT_CHECK) .and() .withExternal() .source(States.WORK_PERMIT_CHECK_PENDING_VERIFICATION) .target(States.WORK_PERMIT_CHECK_FINISHED) .event(Events.FINISH_WORK_PERMIT_CHECK) .and() .withFork() .source(States.IN_CHECK) .target(States.SECURITY_CHECK_STARTED) .target(States.WORK_PERMIT_CHECK_STARTED) .and() .withJoin() .source(States.SECURITY_CHECK_FINISHED) .source(States.WORK_PERMIT_CHECK_FINISHED) .target(States.ALL_CHECKS_FINISHED) .and() .withExternal() .source(States.ALL_CHECKS_FINISHED) .target(States.APPROVED) .and() .withExternal() .source(States.APPROVED) .target(States.ACTIVE) .event(Events.ACTIVATE); } } ; EnableStateMachineFactory
EnableStateMachineFactory annotation create a state machine factory that can be used later to create state machine preferably with id of the enitity that you try to manage/change its state. Before changing the state of the entity , build the statemachine from current entity state.
@Autowired private StateMachineFactory factory; Private StateMachine<States, Events> buildStateMachine(Employee employee) StateMachine sm = factory.getStateMachine(employee.getId()+""); sm.stop(); sm.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess>() { @Override public void apply(StateMachineAccess sma) { sma.addStateMachineInterceptor(aChangeInterceptor); String state = ..... ; //state of the entity sma.resetStateMachine(new DefaultStateMachineContext<States, Events>(States.valueOf(state), null,null, null,null)); } }); sm.start(); return sm; }
Fire events to state machine
private boolean sendEvent(Long id, StateMachine<States, Events> sm, Events event){ return sm.sendEvent( MessageBuilder.withPayload(event) .setHeader(ENTITY_ID_HEADER, id).build()); }
Intercept State post change to update entity state after event is fired . this is part of buildSateMacine() above :
sma.addStateMachineInterceptor(aChangeInterceptor);
References:
[1] https://www.baeldung.com/spring-state-machine
[2] https://docs.spring.io/spring-statemachine/docs/3.2.0/reference/#statemachine-faq
[3] https://medium.com/@hareesh.veduraj/spring-boot-using-spring-state-machine-1c5a6d35b9ad
[4] https://www.youtube.com/watch?v=M4Aa45Gpc4w
State Changes
How can I automatically transit to the next state?
You can choose from three approaches:
- Implement an action and send an appropriate event to a state machine to trigger a transition into the proper target state.
- Define a deferred event within a state and, before sending an event, send another event that is deferred. Doing so causes the next appropriate state transition when it is more convenient to handle that event.
- Implement a triggerless transition, which automatically causes a state transition into the next state when state is entered and its actions has been completed.