Using the Spring state machine in a practical example

  • Tutorial

Using the Spring state machine on the example of the ROSEU protocol


The article describes the use of the Spring state machine using an example of establishing a connection according to ROSEU technology. A connection is established between two EDO operators in point-to-point mode or through a roaming center. It describes how to manage states, switch states by event, and perform specified actions when changing states. If interested, then I ask for a cat.

image

The ROSEU protocol is described in detail here .

For an article, we only need to know the principle of establishing a connection between two EDI clients. Each of them sends an invitation to establish a connection. The type of invitation is either “Request” or “Break”.

To establish a connection, both participants in the workflow must send an invitation of the “Request” type. After that, the connection is considered established.

If one of the participants sends an invitation of type “Break”, then the connection is disconnected.

In our system, we set seven possible statuses for EDI participants.

  1. Connection failed - NO_CONNECTION
  2. Our client has sent an invitation to roaming. But we have not delivered it yet. It is justified by the asynchrony of the institution of the application and its sending to the roaming center. - INVITATION_SAVED
  3. Connection successfully established - ARE_CONNECTED
  4. Connection terminated at the initiative of one of the participants - CONNECTION_BROKEN
  5. An external invitation came, our client did not send anything before - INVITATION_RECEIVED
  6. Invitation of our client accepted by roaming center - INVITATION_SEND
  7. Connection Error - CONNECTION_ERROR

Possible events:

  1. Our client sent an invitation to “Request”. - OUTCOME_INVITATION
  2. Our client sent an invitation “Break” - OUTCOME_REJECT
  3. An external client sent an “Request” invitation - INCOME_INVITATION
  4. The external client sent a “Break” invitation - INCOME_REJECT
  5. Roaming Center successfully accepted the invitation - RC_SUCCESS
  6. Roaming center did not accept the invitation - RC_ERROR

Status switching table. The first line is the initial status. The first column is an event. At the intersection - a new status.

image

Such status switching can be encoded through switch, if, if-else. But through the state machine, in my opinion, this will be more convenient.

The logic is as follows - if someone disconnected, then anyone can re-establish it. If an error occurred while establishing the connection, then nothing more can be done, manual correction of the problem is required.

Detailed documentation on the Spring state machine was taken from here .

We will create the car through the builder

StateMachineBuilder.Builder builder = StateMachineBuilder.builder();

Next, we set all possible statuses. The initial status is set to the current status of customers.

builder.configureStates()
       .withStates()
       .initial(initialState)
       .states(EnumSet.allOf(ClientsState.class));

We configure autostart of the car. Otherwise, you will have to manually start


builder.configureConfiguration()
       .withConfiguration()
       .autoStartup(true);

Next, we prescribe tranches. source - initial status, target - final status, event - event at which status switching occurs, action - updates client statuses.

builder.configureTransitions()
       .withExternal()
       .source(NO_CONNECTION)
       .target(INVITATION_RECEIVED)
       .event(INCOME_INVITATION)
       .action(updateStateAction)
       .and()
       .withExternal()
       .source(NO_CONNECTION)
       .target(CONNECTION_BROKEN)
       .event(INCOME_REJECT)
       .action(updateStateAction)

After creating the state machine, we pass event to its input . But we in action need additional information to update the status of customers. Therefore, we wrap the event in message and put the necessary data in the header .

StateMachine sm = builder.build();
Map clients = new HashMap<>();
clients.put("client1", "client11");
clients.put("client2", "client22");
MessageHeaders headers = new MessageHeaders(clients);
Message message = new GenericMessage<>(event, headers);
sm.sendEvent(message);
sm.stop();

Further in action this data is extracted and used.

@Service
public class UpdateStateAction implements Action {
   @Override
   public void execute(StateContext context) {
       System.out.println("Source state: " + context.getSource());
       System.out.println("Target state: " + context.getTarget());
       System.out.println("Event: " + context.getEvent());
       MessageHeaders headers = context.getMessageHeaders();
       System.out.println(headers.get("client1"));
       System.out.println(headers.get("client2"));
   }
}

You can also use guard to prevent status changes, but in our case this is not necessary.

That's basically it. The source code for the example can be taken from the link .

In the article, we examined how to use the Spring state machine to encode the state transition table.

This is not all of its capabilities, but only the most basic. I hope the article was useful and encourages you to use this framework.

If you have personal experience using it, then welcome to comment.

UPD

During use, an interesting feature was revealed - in case of an exception in one of the default actions, the error is not thrown, but simply logged. In this case, the execution of the entire state machine does not stop. And even the hasStateMachineError method returns false.
As a decision, we made an AbstractAction in which we catch the exception and put it in the state machine context. After that, in case of an exception, hasStateMachineError returns true and then we process it further.


 @Override
    public void execute(StateContext context) {
        try {
            prepareContext(context);
            executeInternal(context);
        } catch (Exception e) {
            logger.error("Ошибка работы state machine", e);
            context.getStateMachine().setStateMachineError(e);
        }
    }

Also popular now: