Utilisation de la machine à états Spring sur l'exemple du protocole ROSEU
L'article décrit l'utilisation de la machine d'état Spring à l'aide d'un exemple d'établissement d'une connexion selon la technologie ROSEU. Une connexion est établie entre deux opérateurs EDO en mode point à point ou via un centre d'itinérance. Il décrit comment gérer les états, changer d'état par événement et effectuer des actions spécifiées lors du changement d'état. Si vous êtes intéressé, je demande un chat.

Le protocole ROSEU est décrit en détail
ici .
Pour un article, il suffit de connaître le principe de l'établissement d'une connexion entre deux clients EDI. Chacun d'eux envoie une invitation à établir une connexion. Le type d'invitation est soit «Demande» soit «Pause».
Pour établir une connexion, les deux participants au workflow doivent envoyer une invitation du type «Demande». Après cela, la connexion est considérée comme établie.
Si l'un des participants envoie une invitation de type "Pause", la connexion est alors déconnectée.
Dans notre système, nous avons défini sept statuts possibles pour les participants EDI.
- La connexion a échoué - NO_CONNECTION
- Notre client a envoyé une invitation à l'itinérance. Mais nous ne l'avons pas encore livré. Elle se justifie par l'asynchronie de l'institution de l'application et son envoi au centre d'itinérance. - INVITATION_SAVED
- Connexion établie avec succès - ARE_CONNECTED
- Connexion interrompue à l'initiative d'un des participants - CONNECTION_BROKEN
- Une invitation externe est venue, notre client n'a rien envoyé avant - INVITATION_RECEIVED
- Invitation de notre client acceptée par le centre d'itinérance - INVITATION_SEND
- Erreur de connexion - CONNECTION_ERROR
Événements possibles:
- Notre client a envoyé une invitation à «Demande». - OUTCOME_INVITATION
- Notre client a envoyé une invitation "Break" - OUTCOME_REJECT
- Un client externe a envoyé une invitation "Demande" - INCOME_INVITATION
- Le client externe a envoyé une invitation "Break" - INCOME_REJECT
- Roaming Center a accepté l'invitation avec succès - RC_SUCCESS
- Le centre d'itinérance n'a pas accepté l'invitation - RC_ERROR
Table de commutation d'état. La première ligne est l'état initial. La première colonne est un événement. À l'intersection - un nouveau statut.

Une telle commutation d'état peut être codée via le commutateur, if, if-else. Mais à travers la machine d'état, à mon avis, ce sera plus pratique.
La logique est la suivante - si quelqu'un se déconnecte, n'importe qui peut le rétablir. Si une erreur s'est produite lors de l'établissement de la connexion, rien de plus ne peut être fait, une correction manuelle du problème est requise.
Une documentation détaillée sur la machine d'état Spring a été prise à
partir d'ici .
Nous allons créer la voiture via le constructeur
StateMachineBuilder.Builder<ClientsState, ContractorEvent> builder = StateMachineBuilder.builder();
Ensuite, nous définissons tous les statuts possibles. Le statut initial est défini sur le statut actuel des clients.
builder.configureStates() .withStates() .initial(initialState) .states(EnumSet.allOf(ClientsState.class));
Nous configurons le démarrage automatique de la voiture. Sinon, vous devrez démarrer manuellement
builder.configureConfiguration() .withConfiguration() .autoStartup(true);
Ensuite, nous prescrivons des tranches.
source - état initial,
cible - état final,
événement - événement auquel le changement d'état se produit,
action - met à jour les états du client.
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)
Après avoir créé la machine d'état, nous transmettons l'
événement à son entrée. Mais nous, en
action, avons besoin d'informations supplémentaires pour mettre à jour le statut des clients. Par conséquent, nous enveloppons l'
événement dans un
message et mettons les données nécessaires dans l'en-
tête .
StateMachine<ClientsState, ContractorEvent> sm = builder.build(); Map<String, Object> clients = new HashMap<>(); clients.put("client1", "client11"); clients.put("client2", "client22"); MessageHeaders headers = new MessageHeaders(clients); Message<ContractorEvent> message = new GenericMessage<>(event, headers); sm.sendEvent(message); sm.stop();
Plus loin dans l'
action, ces données sont extraites et utilisées.
@Service public class UpdateStateAction implements Action<ClientsState, ContractorEvent> { @Override public void execute(StateContext<ClientsState, ContractorEvent> 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")); } }
Vous pouvez également utiliser
guard pour empêcher les changements de statut, mais dans notre cas, cela n'est pas nécessaire.
C'est essentiellement ça. Le code source de l'exemple peut être extrait du
lien .
Dans l'article, nous avons examiné comment utiliser la machine à états Spring pour coder la table de transition d'état.
Ce ne sont pas toutes ses capacités, mais seulement les plus élémentaires. J'espère que l'article vous a été utile et vous encourage à utiliser ce cadre.
Si vous avez une expérience personnelle de son utilisation, n'hésitez pas à commenter.
UPD
Pendant l'utilisation, une fonctionnalité intéressante a été révélée - en cas d'exception dans l'une des actions par défaut, l'erreur n'est pas levée, mais simplement enregistrée. Cependant, l'exécution de l'ensemble de la machine d'état ne s'arrête pas. Et même la méthode hasStateMachineError renvoie false.
Comme décision, nous avons fait une AbstractAction dans laquelle nous interceptons l'exception et la plaçons dans le contexte de la machine à états. Après cela, en cas d'exception, hasStateMachineError renvoie true et nous le traitons ensuite.
@Override public void execute(StateContext<StatesEnum, OperationsEnum> context) { try { prepareContext(context); executeInternal(context); } catch (Exception e) { logger.error(" state machine", e); context.getStateMachine().setStateMachineError(e); } }