gpengtao
gpengtao

Reputation: 225

Spring Statemachine is stateful?

I'm leaning ssm, blow is my demo config:

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

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
        states.withStates()
                .initial(States.S_1)
                .state(States.S_1, myAction(), null)
                .end(States.S_END)
                .states(EnumSet.allOf(States.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
        transitions
                .withExternal()
                .source(States.S_1).target(States.S_2).event(Events.E1).action(myAction());
    }

I send two event to machine, but it run onece.

stateMachine.sendEvent(Events.E1);
stateMachine.sendEvent(Events.E1);

Does the ssm has state, how can I make it run stateless?

I just want to use it define my business procedure.

Upvotes: 2

Views: 1396

Answers (3)

aminator
aminator

Reputation: 481

unfortunately the spring state machine cannot be run stateless. I also hope that this feature will be implemented and that the state machine can be run with a fully externalized state.

So long the solution is this:

  1. enable the spring state machine factory within the spring context:
   @Configuration
   @EnableStateMachineFactory
   public class DomainStateMachineFactory extends
      EnumStateMachineConfigurerAdapter<States, Events> {
    
    @Overrides...
   }
  1. in your service layer process the events like this:
   @Service
   public class DomainStateService {

     @Autowired
     StateMachineFactory<States, Events> stateMachineFactory;

     @Autowired
     private DomainStateMachinePersister persister;

     public StateTransitionEvaluationHolder processEvent( DomainEntity entity, Events event ) {

        // create a brand new state machine
        StateMachine<StatusDto, StatusEventDto> stateMachine = stateMachineFactory.getStateMachine();

        // load the the state of your domain entity into the state machine
        StateMachine<StatusDto, StatusEventDto> restoredStateMachine =
                persister.restore( stateMachine, entity );

        // register a state changed listener
        restoredStateMachine.addStateListener( 
            new DomainStateMachineListener( entity ) );

        // start the machine
        restoredStateMachine.startReactively().block();

        // process the event
        restoredStateMachine.sendEvent( Mono.just( MessageBuilder.withPayload( event ).build() ) )
                            .blockFirst();

        // stop the machine
        restoredStateMachine.stopReactively().block();
     }
   }
  1. the loading of the domain entity state into the state machine is processed by these 2 classes:

a)

   @Component
   public class DomainEntityPersist implements
       StateMachinePersist<States, Events, DomainEntity> {

     @Override
     public void write( StateMachineContext<States, Events> stateMachineContext,
         DomainEntity entity ) {

       throw new StateMachineException( "Persistence not supported. Persisted state remains in Domain Entity." );
     }

     @Override
     public StateMachineContext<States, Events> read( DomainEntity entity ) {

       ExtendedState extendedState = new DefaultExtendedState();
       return new DefaultStateMachineContext<>( entity.getState(), null,
                                             null, extendedState, null,
                                             DomainStateMachineFactory.MACHINE_ID );
     }
   }

and b)

   @Component
   public class DomainEntityStateMachinePersister {

     DomainEntityPersist persist;

     StateMachinePersister<States, Events, DomainEntity> persister;

     public DomainEntityStateMachinePersister ( DomainEntityPersist persist ) {

       this.persist = persist;
       this.persister = new DefaultStateMachinePersister<>( persist );
     }

     public void persist( StateMachine<States, Events> stateMachine,
         DomainEntity entity ) {

       try {
         persister.persist( stateMachine, entity );
       }
       catch ( Exception e ) {
         throw new StateMachineException( e );
       }
     }

      public StateMachine<States, Events> restore(
          StateMachine<States, Events> stateMachine, DomainEntity entity ) {

        try {
          return persister.restore( stateMachine, entity );
        }
        catch ( Exception e ) {
          throw new StateMachineException( e );
        }
      }
    }
  1. the listener looks like this:
    public class DomainStateMachineListener extends
        StateMachineListenerAdapter<States, Events> {

      boolean changed = false;
      DomainEntity entity;

      public DomainStateMachineListener( DomainEntity entity ) {
        this.entity = entity;
      }

      @Override
      public void stateChanged( State<Statues, Events> from,
          State<States, Events> to ) {
        entity.setState( to.getId() );
        setChanged( true );
      }
    }

An exhaustive and more detailed explanation can be found here:

Upvotes: 1

state machine are statefull as goal is to react on a saved state... And event does state change...

I think in your case you might not want a state machine but a "simple" service layer

Upvotes: 1

Janne Valkealahti
Janne Valkealahti

Reputation: 2646

Well it looks like event E1 takes machine from S_1 to S_2. Sending that event again will not do anything as machine is already in state S_2 and transition from S_1 to S_2 cannot happen.

Not sure what you mean by making machine stateless?

Upvotes: 3

Related Questions