How to restore state machine from context builded in runtime?
Asked Answered
B

1

1

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?

Bobodioulasso answered 15/2, 2019 at 9:41 Comment(0)
C
9

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:

  • Resetting the StateMachine in all regions
  • Load Order status from DB
  • Create new DefaultStateMachineContext and populate accordingly

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));
  });
}
Choric answered 15/2, 2019 at 10:15 Comment(8)
But why I need to recreating a state machine on the fly? I can work with already existing instance of the state machine and rehydrate that for every Order model, processing them one by one?Bobodioulasso
yes sure - you can do that. The logic applies to a single state machine. How you obtain an instance depends on the use cases. In my example it's just through a factory - that's all.Choric
What is the cost of stopping and starting statemachine ? Is it resource/memory intensive ?Natie
Developers of the Spring SM are stating that they have designed the state machines to be very light-weight objects and add minimal performance overhead. Of course every usecase should be considered separately. Best approach is to build a test-case suite that simulates users and then you can test different implementations and have metrics data.Choric
@hovanessyan: how to rehydrate statemachine with nested regionsChow
in the same way - there's no difference in the codeChoric
@Choric doing this causes all transitions defined using @ OnTransition to stop working...Propylene
@hovanessyan, works fine now with some minor tweak #73845848Propylene

© 2022 - 2024 — McMap. All rights reserved.