Hot questions for Spring Statemachine

Top 10 Java Open Source / Spring / Spring Statemachine

Question:

We are considering using Spring State Machine for the following use case:

One of our entities (i.e. a JPA entity from our domain model) can be in one of a number of states and we have millions such entities (and as many lines in our database).

We are considering using:

org.springframework.statemachine.data.jpa.JpaStateRepository
  • Should we annotate our domain model classes with JpaRepositoryState and thereby create a dependency between our domain model and spring state machine?

  • What would be an alternative to the above i.e. ensure that our JPA entity class is not too tightly coupled to JpaRepositoryState?

  • What is the mapping/relationship between the state machine's machineId and the JPA entity's @Id?


Answer:

JpaRepositoryState really doesn't have anything to do with your domain model as it is our entity class to store machine configuration in an external repository. Specifically it is a state representation and similarly there are entity classes for transitions, actions and guards.

There's no relationship between @id and machineId. @id is just a field identifying row in a database, which is autogenerated if you store entities manually via spring-data. Fields machineId and submachineId are however used together so that you're able to define multiple machines in a repository and then make a substate to reference a machine similarly how in UML you can define a plain state and then define that to be a reference to a submachine.

It seems that I'm getting more and more questions related user's entity classes and how to handle those with a statemachine, like in gh453. I don't really have an answer to this right now as Spring Statemachine was never designed to handle these specific use cases. This doesn't mean that Spring Statemachine would never handle these scenarious, we just don't have anything out of the box right now.

Also our documentation is lacking these topics which is a clear indication that we need to be better on that area.

Question:

11.5 Configuring Transitions We support three different types of transitions, external, internal and local. Transitions are either triggered by a signal which is an event sent into a state machine or a timer. I donot konw what is different .


Answer:

Internal

You can think of internal transition as self-transition - from A to A; Source and target state are always the same.

Local and External

Most of the time these two are equivalent, with the exception of when transitioning between super and sub-states. Local transition doesn’t cause exit and entry to source state if target state is a substate of a source state or if the target is a superstate of a source state.

Please consult the official UML specification (section 14.5.11 - Transition class, especially - Constraints section, where the above is formally defined) upon which the Spring state machine is implemented.

Question:

I'm creating a state machine object using the provided builder as follows:

10.2 State Machine via Builder I'm seeing the following exception:

ERROR org.springframework.statemachine.support.StateMachineObjectSupport - Unable to initialize annotation handlers java.lang.IllegalStateException: Bean factory must be instance of ListableBeanFactory, was null

The exception isn't preventing the state machine from functioning as expected. However, I would like to get down to the bottom of why I'm seeing this.

Anyone know how I can stop this exception from showing?

Thanks.


Answer:

Managed to get a solution for this.

I autowired the the Spring application context into my class. I then extracted the AutowireCapableBeanFactory from this and set this up inside the builder. As follows:

   @Autowired
    private ApplicationContext appContext;

     private void buildStateMachine() throws Exception {
        Builder<EnquiryStatus, Event> builder = StateMachineBuilder.builder();
        builder.configureConfiguration().withConfiguration().beanFactory(appContext.getAutowireCapableBeanFactory());
     }

Question:

When I receive a request on my API, I want to do a series of steps, each being a check or an enrichment. Each step could either succeed or fail. On Success, the next step should be carried out. On Failure, an end-step should be executed, and the flow is done. For that I have considered Spring State Machine, as it seems to fit the bill.

I have read up on the documentation and played around with it, but some things elude me:

  1. Should there be a 1-to-1 relationship between a request and a State Machine, meaning that for every request, I make a new State Machine instance? Or should I somehow reuse a completed State Machine by resetting the machine for the next request?

  2. What about cleanup of completed State Machines? There doesn't seem to be a way to destroy and clean a State Machine instance. If I create 1 per request, I've effectively introduced a memory leak, unless the framework somehow handles resources.


Answer:

There is no absolutely correct answer to your question so I just need to leave some comments here. State machine as a concept is so loose that it gives you so many different ways to do things.

  1. Whole concept if steps one after another kinda relates to how tasks recipe was implemented. It executes a dag of tasks and if parent task fails machine enters into error state giving user a chance to fix things and request machine to continue. statemachine-recipes-tasks statemachine-examples-tasks. Might be that this kind of use case would be a good candidate to create a new recipe as it is pretty generic.
  2. Framework should clear things after machine has been stopped and eventually jvm should clear garbage. If you find something abnormal, please file a gh issue and we'll fix things.
  3. We have sample statemachine-examples-eventservice which is reusing machines but I'm currently re-implementing that sample(it works but should be implemented better) as I was told by our head-chef that what I did there is dump SPR-15042. Machines cannot be used with a session scope and things go south if rich object(which ssm is) is serialised.
  4. It is relatively easy to do a combination of states and choices which would do your step flow. It's only question how much you want this to be re-usable(thus generic recipe would be a good thing, PR's welcomed :) )
  5. What comes for error handling something I presented in a statechart in gh-240 is also something to consider.
  6. There has been some questions if ssm could work as a more generic flow engine but it's probably something it's never going to be as it would be a completely new project. Thought most of a flows could be handled as a separate recipes.

Question:

I am using Spring Statemachine to provide a user's workflow. I need to persist the state changes so that user's state is not lost across restarts. Now, I can do this based on the examples provided, however one thing missing is how to recreate the state if a crash does occur.

Basically, I want to create the state machine and tell it set itself to the last state it had before the crash and copy any extended state variables from the database. Is there a way to do this?


Answer:

Maybe this can help you:

stateMachine
                .getStateMachineAccessor()
                .doWithAllRegions(access -> {
                    access.resetStateMachine(new DefaultStateMachineContext<>({ResetState}, null, null, null, null));
                });
stateMachine.start();
stateMachine.sendEvent({NewEventFromResetState});

Question:

It seems that actions added to choice pseudo-states are silently ignored. Doing this:

Builder builder = StateMachineBuilder.builder();
StateConfigurer states = builder.configureStates().withStates();
StateMachineTransitionConfigurer transitions = builder.configureTransitions();
StateConfigurer statesConfig = states.initial(INITIAL).states(EnumSet.allOf(StateType.class));
statesConfig.choice(StateType.CHOICE_STATE);

transitions.withChoice().source(StateType.CHOICE_STATE). //
    first(StateType.S1, someGuard). //
    last(StateType.S2);

states.state(StateType.CHOICE_STATE, someAction, null);

Results in someAction never being executed when CHOICE_STATE is entered.

Adding actions to transitions out of CHOICE_STATE (for example, to S1 or S2 above) is simply not permitted by the framework.

To get around this, we have implemented a state that precedes CHOICE_STATE. We are then free to add actions to this state, as usual. I was just wondering what is the reason for this limitation, or if there is some way of putting actions on a pseudo-state that I may have missed.


Answer:

That's because choice is a pseudostate which is supposed to be transitient so there should not be behavioural changed in that state itself.

Have you tried to define Action with a Transition which takes you into a choice state?

@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
  transitions
    .withExternal()
      .source(TestStates.S1)
      .target(TestStates.S2)
      .event(TestEvents.E2)
      .action(externalTestAction());
}


@Bean
public Action<TestStates, TestEvents> externalTestAction() {
  return new TestAction();
}

Preceding state is a good workaround, especially it that is accompanied with a triggerless transition

I can try to see if in Spring Statemachine we could add feature to a transition(configurer for choice transition) itself. I created a ticket for this https://github.com/spring-projects/spring-statemachine/issues/108.

While we're mostly trying to follow UML model, spec is very vague in most parts and leave a lot of implementation specifics to the implementation itself.