Investigando sobre arquitecturas CQRS encontre el Axon Framework. Te comparto mis apuntes en mi camino a aprender este framework.
¿Que es CQRS?
CQRS es una forma de crear sistemas de software que hace hincapié en separar la parte que cambia el estado de la aplicación y la parte que consulta el estado de la aplicación.
¿Que es el Framework Axon?
Es un framework para implementar CQRS en Java. Se describe asi mismo como un framework que te permite construir aplicaciones escalables, extensibles y mantenibles; permitiendo a los desarrolladores aplicar el patrón arquitectónico Command Query Responsibility Segregation (CQRS). El cual nos da ciertos bloques importantes con sus implementaciones respectivas para crear estos sistemas como: agregados, repositorios y bus de eventos (el mecanismo de envío para eventos). Estos últimos términos provienen del Domain Driven Design (DDD).
En palabras simples: CQRS es escribir y leer en forma separada.
En búsqueda de tener bajo acoplamiento, el trabajo basado en componentes, colaboración y comunicación por medio de mensajes es hoy por hoy una de las formas mas populares de conseguir CQRS.
Una aplicación CQRS en general usa los siguientes componentes: UI, Bus de Comandos, Manejador de Comandos, Repositorios, Objetos de Dominio, Bus de Eventos, Manejador de Eventos, y todo ello conectado por medio de comandos y eventos.
Los componentes de arriba cambian el estado de la aplicación (aquí ira la lógica de nuestra aplicación) y los componentes de abajo consulta el estado de la aplicación.
Los usuarios usan los componentes de UI que intentarán cambiar el estado (reservar un ticket de avión, editar el profile de tu cuenta, modificar un carrito de compra, etc). Esa intención es modelada con Comandos. Esos comandos son pasados al Bus de Comandos que buscará el Manejador de Comandos necesario para poder atender el comando iniciado por el usuario.
El cambio de estado es manejado dentro del Manejador de Comando, el cual, usa el Repositorio para cargar los objetos del Domino y ejecutar la lógica de negocio, aplicando de esta manera los cambios de estado respectivos.
Una vez que hay cambio de estado se produce un Evento, el cual es publicado en el Bus de Eventos.
Este Bus de Eventos se asegura que los Manejadores de Eventos necesarios son notificados. Estos manejadores de eventos modifican la representación de el estado de la aplicación, el cual luego el UI puede consultar. También es posible, que como respuesta a un evento, algunos nuevos comandos sean iniciados.
¿Dónde entra el Event Sourcing?
La idea detrás de esto, es que en lugar de guardar el estado actual de la aplicación, nosotros guardamos los eventos que modifican la aplicación.
Event Sourcing puede facilmente ser usado en un sistema CQRS, pero, esto no implica que un sistema CQRS debá usar event sourcing. El Axon framework facilita el uso de event sourcing.
Ejemplo: Banco
Vamos a simular el funcionamiento de un Banco, en donde vamos a implementar estas operaciones:
- Create (crear cuenta bancaria)
- AccountCreated
- WithDrawMoney (retiro)
- MoneyWithdrawn
- AccountOverdrawn (Exception)
- DepositMoney (deposito)
- MoneyDeposited
El código fuente estará en el siguiente link de github.
Creamos nuestro ejemplo con start.spring.io:
Lo abrimos con el Intellij IDEA y tendremos esta primera versión:
Pasamos a agregar las dependencias de Axon Framework 3.3.x:
Creamos el Core API usando Kotlin:
Para que Kotlin funcione bien agregamos sus dependencias y el plugin para trabajar con Kotlin:
Vamos a crear un Agregado llamado Account, pero, primero haremos su Test Case:
Se nos pide confirmar el test y los métodos que deseamos nos genere:
La implementación del primer test es sobre como crear una cuenta de banco:
Si ejecutamos la prueba obtenemos el siguiente error:
org.axonframework.commandhandling.NoHandlerForCommandException: No handler was subscribed to command [pe.joedayz.sample.axonbank.coreapi.CreateAccountCommand]
Esto es natural porque el Agregado Account no ha sido implementado y como dice el mensaje no hay manejador para el comando CreateAccountCommando.
Implementamos el Agregado Account:
Ejecutamos el test y obtenemos exito:
Ahora vamos a implementar el caso de prueba para retiro:
Si ejecutamos obtenemos el error:
java.lang.IllegalArgumentException: Invalid command. It does not identify the target aggregate. Make sure at least one of the fields or methods in the [WithdrawMoneyCommand] class contains the @TargetAggregateIdentifier annotation and that it returns a non-null value.
Para ello modificamos la clase Kotlin para que dicho Comando tenga esa anotación:
Desarrollamos un nuevo test para probar un retiro con una cantidad absurda:
Dicho test podrá ejecutarse implementando el Agregado Account:
y la clase OverdraftLimitExceededException extends Exception.
Ejecutamos el Test y obtenemos el exito esperado:
Desarrollamos un nuevo test que prueba dos retiros simultaneos, pero, donde el balance es incorrecto:
Desarrollamos un nuevo test que prueba dos retiros simultaneos, pero, donde el balance es incorrecto:
Ejecutamos el Test y obtenemos el exito esperado:
Application
Creamos una clase adicional Application para configurar nuestro Agregado Account, un almacen de eventos en memoria y ejecutamos algunos comandos para ver si funciona la lógica de overflow.
El cual como vemos en el log crea hilos para ejecutar los comandos.
Eso fue para probar. Ahora configuraremos Axon en el proyecto Spring Boot:
Con esto logramos un bus de comandos, un bus de eventos auto configurado para nuestro proyecto. Por ahora es suficiente.
Pasamos a crear un Event Store:
Dicha anotación @EnableAxon también hace un scan por Agregados. Por esta razón usaremos la anotación @Aggregate para decorar nuestro agregado.
Si ejecutamos la aplicación en este punto no pasará nada. Por eso vamos a configurar el CommandBus y a hacer la misma prueba que hicimos en la clase anterior.
Obtenemos el mismo resultado: pe.joedayz.sample.axonbank.account.OverdraftLimitExceededException: null
Si observamos el log todo se hace en el hilo principal, debido a ello, vamos a configurar un CommandBus asincrono.
Al ejecutar encontramos una exception: No encuentra nuestro agregado.
Y eso es por el orden de creación del comando, como ahora es asincrono, falla si no encuentra el agregado. Para evitar eso, hacemos el siguiente cambio:
Y al ejecutar todo en orden:
Nosotros podemos hacer más personalización como el definir un repository para nuestro Agregado Account.
Pero también hay que crear un bean para transacciones y por ahora no usaremos el command bus.
Listo eso es todo por ahora.
Enjoy!
Joe