2

Let us say we have 3 states S1, S3, S3 and an error state E1. The S* states have actions that execute A1, A2, A3 respectively. If any of A1, A2, A3 results in an exception being thrown, the state machine should go to E1. Is there a way I can define a common transition that says if any action throws an exception, go to E1? Or do I have to explicitly state the transition for each state

states.withStates()
                .initial(START)
                .end(END)
                .end(ERROR)
                .state(S1, A1())
                .state(S2, A2())
                .state(S3, A3())



//Is this the only way of defining error transition? Can I dry it up?
    transitions
       .withExternal()
       .source(S1)
       .event(ErrorEvent)
       .target(E1)

     .and()

        .withExternal()
           .source(S2)
           .event(ErrorEvent)
           .target(E1)

    .and()

        .withExternal()
          .source(S3)
          .event(ErrorEvent)
          .target(E1)

I was hoping for a way to dry this up somehow. If I have 10 states, then it becomes rather too repetitive to do it this way.

sat
  • 5,489
  • 10
  • 63
  • 81
  • I also have looked for a solution to this problem, but I was not able to find anything useful. I think it's not possible to do using the SM config adapter. – hovanessyan Feb 17 '19 at 08:43

1 Answers1

1

There's currently no common way using the State Machine builder to do that. By definition each state transition should be explicitly defined in the configuration.

It's another question to discuss if this is a correct approach in the SM world:

If any of A1, A2, A3 results in an exception being thrown, the state machine should go to E1.

It's debatable if the SM should go to an error state or should stay in the same state or revert to a previous state and so on. My understanding is that having a common error state to transition to, when error happens in any other state, is not a good approach.

It really depends on what you need to do with the error. For example if you want to signal on the calling code or propagate the error outside of the stateMachine, you can use the StateContext or the SM ExtendedContext for that. You can insert a custom flag (e.g. "hasError") and the Exception itself, and expose that to your caller code, but this is not very elegant. Someone should check the presence of that flag on each invocation and notify the caller code (or the caller code itself should check that, and it's ugly).

Another approach would be to clear the stateContext and stay in the same state, waiting for some retry logic to kick in. Or in your errorAction you can send the ErrorEvent to your SM and pass whatever info is needed in the SM Context, which I believe is the approach you've chosen.

Either way - all transitions should be present in the StateMachineModel. This model can be created using the builder approach, or you can create it programatically (where will be easier for example to iterate across all existing states and just define 1 extra transition to the error state).

@Override
public StateMachineModel<String, String> build() {
    ConfigurationData<String, String> configurationData = new ConfigurationData<>();
    Collection<StateData<String, String>> stateData = new ArrayList<>();
    stateData.add(new StateData<String, String>("S1", true));
    stateData.add(new StateData<String, String>("S2"));
    stateData.add(new StateData<String, String>("S3"));
    stateData.add(new StateData<String, String>("ERROR_STATE"));
    StatesData<String, String> statesData = new StatesData<>(stateData);
    Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
    transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
    stateData.forEach(state -> {
        if (state.getState() != ERROR_STATE) {
            transitionData.add(new TransitionData<String, String>(state.getState(), "ERROR_STATE", "ERROR_EVENT"));
        }
    });
    TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);
    StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<String, String>(configurationData,
            statesData, transitionsData);
    return stateMachineModel;
}
hovanessyan
  • 30,580
  • 6
  • 55
  • 83