Search code examples
spring-bootspring-statemachinestate-machine-workflow

Spring state machine configuration for rest service requests


I have a simple order processing application and trying to apply spring state machine for handling states of the order. And I wondering how can I handle order's states for the same order during multiple request from rest service.

Order states:

enum OrderEvents {
    FULFILL,
    PAY,
    CANCEL
}

Order events:

enum OrderStates {
    SUBMITTED,
    PAID,
    FULFILLED,
    CANCELLED    
}

State machine configuration:

@Log
@Configuration
@EnableStateMachineFactory
class SimpleEnumStatemachineConfiguration extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                .withExternal().source(OrderStates.SUBMITTED).target(OrderStates.PAID).event(OrderEvents.PAY)
                .and()
                .withExternal().source(OrderStates.PAID).target(OrderStates.FULFILLED).event(OrderEvents.FULFILL)
                .and()
                .withExternal().source(OrderStates.SUBMITTED).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
                .and()
                .withExternal().source(OrderStates.PAID).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL);
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        states
                .withStates()
                .initial(OrderStates.SUBMITTED)
                .state(OrderStates.PAID)
                .end(OrderStates.FULFILLED)
                .end(OrderStates.CANCELLED);
    }

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

In my order service I call

StateMachine<OrderStates, OrderEvents> sm = this.factory.getStateMachine(orderIdKey);

But it seems that every time is new state machine created even for the same orderIdKey. So, how can I get access to state machine created when order was submitted on next state?


Solution

  • You have basically two options:

    a) persist the state machine for the given orderId, using a state machine persister as explained here.

    b) create a new state machine for given orderId (per HTTP request) and rehydrate the SM state based on the state of the order entity for the given orderId. SM objects are considered lightweight, so this is a viable approach as well. Below is a code sample:

    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));
      });
    }