En este tutorial sencillo, crearemos un par de microservicios en Spring Boot y organizaremos la interacción entre ellos a través del marco de trabajo Axon.

Supongamos que tenemos esta tarea.
Hay una fuente de transacciones en el mercado de valores. Esta fuente nos transmite las transacciones a través de una API REST.
Necesitamos obtener estas transacciones, almacenarlas en una base de datos y crear un almacenamiento en memoria conveniente.
Este almacenamiento debe cumplir las siguientes funciones:
- devolver una lista de operaciones;
- devolver la posición completa, es decir, una tabla de "instrumento" — "cantidad actual de títulos";
- devolver la posición para un instrumento específico.
¿Cómo abordaremos la solución a esta tarea?
Siguiendo los preceptos de la moda de microservicios, debemos dividir la tarea en microservicios compuestos:
- obtener transacciones a través de REST;
- almacenar las transacciones en la base de datos;
- almacenamiento en memoria para presentar los datos de la posición.
En el marco de este tutorial, haremos el primer y el tercer servicio, dejando el segundo para la segunda parte (escriba en los comentarios si esto le interesa).
Así que tenemos dos microservicios.
El primero obtiene datos del exterior.
El segundo procesa estos datos y responde a las solicitudes entrantes.
Por supuesto, queremos lograr escalabilidad horizontal, actualizaciones sin tiempo de inactividad y otras ventajas de los microservicios.
¿Cuál es la tarea, bastante difícil, que tenemos por delante?
En realidad son muchas, pero ahora hablemos de cómo fluirán los datos entre estos microservicios. También podemos hacer REST entre ellos, podemos poner alguna cola, hay muchas ideas con sus pros y contras.
Consideremos uno de los enfoques posibles: interacción asincrónica a través de Marco Axon.
¿Cuáles son las ventajas de esta solución?
Primero, la interacción asincrónica aumenta la flexibilidad (sí, hay una desventaja, pero por ahora solo estamos hablando de ventajas).
En segundo lugar, obtenemos directamente Event Sourcing y CQRS.
En tercer lugar, Axon proporciona una infraestructura lista para usar, y solo necesitamos concentrarnos en el desarrollo de la lógica de negocio.
Comencemos.
Nuestro proyecto estará basado en Gradle. Tendrá tres módulos:
- common. módulo con estructuras de datos comunes (no nos gusta copiar y pegar);
- tradeCreator. módulo con el microservicio para recibir transacciones a través de REST;
- tradeQueries. módulo con el microservicio para mostrar la posición.
Tomaremos Spring Boot como base y conectaremos el starter de Axon.
Axon funciona excelentemente sin Spring, pero los utilizaremos juntos.
Aquí es necesario detenerse y comentar un poco sobre Axon.
Es un sistema cliente-servidor. Hay un servidor, que es una aplicación independiente, la vamos a ejecutar en Docker.
Y hay clientes que se integran en microservicios.
Así que la imagen es la siguiente. Primero se inicia el servidor Axon (en Docker), luego nuestros microservicios.
Al iniciar, los microservicios buscan el servidor y comienzan a interactuar con él. La interacción se puede dividir condicionalmente en dos tipos: técnica y de negocio.
Técnica: es el intercambio de mensajes como 'estoy vivo' (se pueden ver esos mensajes en modo de depuración).
De negocio: son mensajes como 'nuevo trato'.
Una característica importante es que, después de iniciar, el microservicio puede preguntar al servidor Axon '¿qué ha ocurrido?' y el servidor envía al microservicio los eventos acumulados. De esta manera, el microservicio puede reiniciarse de manera bastante segura sin pérdida de datos.
Con este esquema de intercambio, podemos iniciar muchos instancias de microservicios muy fácilmente,
incluso en diferentes hosts.
Sí, una instancia del servidor Axon no es fiable, pero por ahora es así.
Trabajamos en las paradigmas de Event Sourcing y CQRS. Esto significa que debemos tener 'comandos', 'eventos' y 'consultas'.
Tendremos un comando: 'crear trato', un evento 'trato creado' y tres consultas: 'mostrar todos los tratos', 'mostrar posición', 'mostrar posición por instrumento'.
El esquema de trabajo resulta ser el siguiente:
- El microservicio tradeCreator recibe el trato a través de Rest.
- El microservicio tradeCreator crea el comando 'crear trato' y lo envía al servidor Axon.
- El servidor Axon recibe el comando y lo reenvía al destinatario interesado, en nuestro caso, el microservicio tradeCreator.
- El microservicio tradeCreator recibe el comando, forma el evento 'trato creado' y se lo envía al servidor Axon.
- El servidor Axon recibe el evento y lo reenvía a los suscriptores interesados.
- Actualmente solo tenemos un destinatario interesado: es el microservicio tradeQueries.
- El microservicio tradeQueries recibe el evento y actualiza sus datos internos.
(Es importante que en el momento de la formación del evento, el microservicio tradeQueries puede no estar disponible, pero tan pronto como se inicie, recibirá el evento de inmediato).
Sí, el servidor Axon está en el centro de las comunicaciones, todos los mensajes pasan a través de él.
Pasemos a la codificación.
Para no saturar la publicación con código, a continuación solo incluiré fragmentos, la enlace al ejemplo completo estará más abajo.
Empecemos con el módulo común.
En él, las partes comunes son el evento (class CreatedTradeEvent). Tenga en cuenta la nomenclatura, esencialmente es el nombre del comando que generó este evento, pero en pasado. En pasado porque primero aparece el comando, que lleva a la creación del evento.
Otras estructuras comunes son las clases para describir una posición (class Position), una operación (class Trade) y la parte de la operación (enum Side), es decir, compra o venta.
Pasemos al módulo tradeCreator.
Este módulo tiene una interfaz Rest (class TradeController) para recibir operaciones.
De la operación recibida se forma el comando 'crear operación' y se envía al servidor axon.
@PostMapping("/trade")
public ResponseEntity create(@RequestBody Trade trade) {
var createTradeCommand = CreateTradeCommand.builder()
.tradeId(trade.getTradeId())
...
.build();
var result = commandGateway.sendAndWait(createTradeCommand, 3, TimeUnit.SECONDS);
return ResponseEntity.ok(result.get().toString());
}
Para procesar el comando se utiliza la clase class TradeAggregate.
Para que Axon lo encuentre, colocamos la anotación @Aggregate.
El método para procesar el comando se ve así (con abreviaciones):
@CommandHandler
public TradeAggregate(CreateTradeCommand command) {
log.info("command: {}", command);
var event = CreatedTradeEvent.builder()
.tradeId(command.tradeId())
....
.build();
AggregateLifecycle.apply(event);
}
Del comando se forma un evento y se envía al servidor.
El comando se encuentra en la clase CreateTradeCommand.
Ahora veamos el último módulo tradeQueries.
Las consultas se describen en el paquete queries.
En este módulo también hay una interfaz Rest.
public class TradeController.
Como ejemplo, veamos el manejo de la solicitud: 'mostrar todas las operaciones'.
@GetMapping("/trade/all")
public List findAllTrades() {
return queryGateway.query(new FindAllTradesQuery(),
ResponseTypes.multipleInstancesOf(Trade.class)).join();
}
Se crea una solicitud de selección y se envía al servidor.
Para procesar la solicitud de selección se utiliza la clase TradesEventHandler.
En él hay un método marcado con la anotación.
@QueryHandler
public List handleFindCurrentPositionQuery(FindCurrentPositionQuery query)
Es el responsable de seleccionar datos del almacenamiento en memoria.
Surge la pregunta de cómo se actualiza la información en este almacenamiento.
Empecemos por decir que es simplemente un conjunto de ConcurrentHashMap, diseñados para consultas específicas.
Para su actualización se aplica el método:
@EventHandler
public void on(CreatedTradeEvent event) {
log.info("event:{}", event);
var trade = Trade.builder()
...
.build();
trades.put(event.tradeId(), trade);
position.merge(event.shortName(), event.size(),
(oldValue, value) -> event.side() == Side.BUY ? oldValue + value : oldValue - value);
}Él recibe el evento "trade creado" y actualiza los Mapas.
Estos son los aspectos clave del desarrollo de microservicios.
¿Qué se puede decir sobre las desventajas de Axon?
En primer lugar, esto complica la infraestructura, se introduce un punto de fallo: el servidor Axon, toda la comunicación pasa a través de él.
En segundo lugar, se manifiesta de manera muy clara una desventaja de este tipo de sistemas distribuidos: la inconsistencia temporal de los datos. En nuestro caso, entre recibir una nueva operación y actualizar los datos para las consultas puede pasar un tiempo inaceptablemente largo.
¿Qué ha quedado fuera de la vista?
No se ha mencionado nada sobre Event Sourcing y CQRS, qué son y para qué sirven.
Sin la explicación de estos conceptos, algunos puntos podrían no haber quedado claros.
Es posible que ciertos fragmentos de código también requieran aclaraciones.
De esto hablaremos en el 21 de septiembre.
.
Fuente: habr.com
