javaspringspring-statemachine

How to restore state machine from context builded in runtime?


I have a state machine

@EnableStateMachine
@Configuration
public class StateMachineConfiguration extends EnumStateMachineConfigurerAdapter<Status, Event> {
    @Override
    public void configure(StateMachineStateConfigurer<Status, Event> states) throws Exception {
        states.withStates()
                .initial(Status.DRAFT)
                .states(EnumSet.allOf(Status.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<Status, Event> transitions) throws Exception {
        transitions

                .withExternal()
                .target(Status.INVITATION).source(Status.DRAFT)
                .event(Event.INVITED)
                .guard(new Guard())
                .action(new ActionInvited())
                .and()

                .withExternal()
                .target(Status.DECLINED).source(Status.INVITATION)
                .event(Event.DECLINED)
                .action(new ActionDeclined());
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<Status, Event> config) throws Exception {
        config.withConfiguration().autoStartup(true);
    }
}

and I have a model, for example Order. Model persists in DB. I extract model from DB, now my model has a status Order.status == INVITATION. I want to continue processing model with statemachine, but instance of statemachine will starts processing with initial state DRAFT but I needs continue processing from status INVITATION. In other words I want to execute

stateMachine.sendEvent(MessageBuilder
  .withPayload(Event.DECLINED)
  .setHeader("orderId", order.id)
  .build()
)

and execute action ActionDeclined(). I don't want to persist a context of state machine in DB. I want to setting a state of stateMachine to state of my Model in runtime. How can I do that in right way? Using DefaultStateContext constructor or have an other, more beautiful way?


Solution

  • One possible approach is to create the StateMachine on the fly and to rehydrate the state machine from the DB using the state of the Order. In this case you need to do the following steps:

    Let's assume you have a build method, which returns new state machines for processing order events (using a StateMachineFactory), but for an existing order, it will rehydrate the state from the database.

    StateMachine<Status, Event> build(long orderId) {
      orderService.getOrder(orderId) //returns Optional
      .map(order -> {
         StateMachine<Status, Event> sm = stateMachineFactory.getStateMachine(Long.toString(orderId));
         sm.stop();
         rehydrateState(sm, sm.getExtendedState(), order.getStatus());
         sm.start();
         return sm;
       })
      .orElseGet(() -> createNewStateMachine(orderId);
    }
    
    
    void rehydrateState(StateMachine<Status, Event> newStateMachine, ExtendedState extendedState, Status orderStatus) {
      newStateMachine.getStateMachineAccessor().doWithAllRegions(sma ->
       sma.resetStateMachine(new DefaultStateMachineContext<>(orderStatus, null, null, extendedState));
      });
    }