Usando la máquina de estado Spring en un ejemplo práctico

Usando la máquina de estado Spring en el ejemplo del protocolo ROSEU


El artículo describe el uso de la máquina de estado Spring usando un ejemplo de establecer una conexión de acuerdo con la tecnología ROSEU. Se establece una conexión entre dos operadores EDO en modo punto a punto o mediante un centro de itinerancia. Describe cómo administrar estados, cambiar estados por evento y realizar acciones específicas al cambiar de estado. Si está interesado, entonces pido un gato.

imagen

El protocolo ROSEU se describe en detalle aquí .

Para un artículo, solo necesitamos conocer el principio de establecer una conexión entre dos clientes EDI. Cada uno de ellos envía una invitación para establecer una conexión. El tipo de invitación es "Solicitar" o "Interrumpir".

Para establecer una conexión, ambos participantes en el flujo de trabajo deben enviar una invitación del tipo "Solicitud". Después de eso, la conexión se considera establecida.

Si uno de los participantes envía una invitación de tipo "Interrupción", la conexión se desconecta.

En nuestro sistema, establecemos siete estados posibles para los participantes de EDI.

  1. Conexión fallida - NO_CONNECTION
  2. Nuestro cliente ha enviado una invitación a roaming. Pero aún no lo hemos entregado. Se justifica por la asincronía de la institución de la aplicación y su envío al centro de roaming. - INVITATION_SAVED
  3. Conexión establecida correctamente: ARE_CONNECTED
  4. Conexión terminada por iniciativa de uno de los participantes - CONNECTION_BROKEN
  5. Llegó una invitación externa, nuestro cliente no envió nada antes - INVITATION_RECEIVED
  6. Invitación de nuestro cliente aceptada por el centro de roaming - INVITATION_SEND
  7. Error de conexión: CONNECTION_ERROR

Posibles eventos:

  1. Nuestro cliente envió una invitación a "Solicitud". - OUTCOME_INVITATION
  2. Nuestro cliente envió una invitación "Break" - OUTCOME_REJECT
  3. Un cliente externo envió una invitación de "Solicitud" - INCOME_INVITATION
  4. El cliente externo envió una invitación de "interrupción" - INCOME_REJECT
  5. Roaming Center aceptó la invitación con éxito - RC_SUCCESS
  6. El centro de roaming no aceptó la invitación - RC_ERROR

Tabla de cambio de estado. La primera línea es el estado inicial. La primera columna es un evento. En la intersección: un nuevo estado.

imagen

Tal cambio de estado se puede codificar a través del interruptor, si, si no. Pero a través de la máquina de estado, en mi opinión, esto será más conveniente.

La lógica es la siguiente: si alguien se desconecta, cualquiera puede restablecerlo. Si se produjo un error al establecer la conexión, entonces no se puede hacer nada más, se requiere la corrección manual del problema.

La documentación detallada sobre la máquina de estado Spring fue tomada de aquí .

Crearemos el auto a través del constructor

StateMachineBuilder.Builder<ClientsState, ContractorEvent> builder = StateMachineBuilder.builder(); 

A continuación, establecemos todos los estados posibles. El estado inicial se establece en el estado actual de los clientes.

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

Configuramos el inicio automático del automóvil. De lo contrario, deberá iniciar manualmente

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

A continuación, prescribimos tramos. fuente : estado inicial, destino : estado final, evento : evento en el que se produce el cambio de estado, acción : actualiza los estados del cliente.

 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) 

Después de crear la máquina de estado, pasamos el evento a su entrada. Pero en acción necesitamos información adicional para actualizar el estado de los clientes. Por lo tanto, ajustamos el evento en el mensaje y colocamos los datos necesarios en el encabezado .

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

Además en acción, estos datos son extraídos y utilizados.

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

También puede usar la protección para evitar cambios de estado, pero en nuestro caso esto no es necesario.

Eso es básicamente todo. El código fuente para el ejemplo se puede tomar del enlace .

En el artículo, examinamos cómo usar la máquina de estado Spring para codificar la tabla de transición de estado.

Estas no son todas sus capacidades, sino solo las más básicas. Espero que el artículo haya sido útil y lo aliente a usar este marco.

Si tiene experiencia personal al usarlo, bienvenido a comentar.

UPD

Durante el uso, se reveló una característica interesante: en caso de una excepción en una de las acciones predeterminadas, el error no se produce, sino que simplemente se registra. En este caso, la ejecución de toda la máquina de estado no se detiene. E incluso el método hasStateMachineError devuelve falso.
Como decisión, hicimos una AbstractAction en la que captamos la excepción y la colocamos en el contexto de la máquina de estado. Después de eso, en caso de una excepción, hasStateMachineError devuelve verdadero y luego lo procesamos más.

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

Source: https://habr.com/ru/post/440788/


All Articles