,

CQRS + Redis + EventStoreDB

CQRS + Redis + EventStoreDB
Avatar de Jorge Fernández

CQRS (Command Query Responsibility Segregation) es posiblemente uno de los patrones más utilizados en entornos basados en microservicios. Su nombre ya nos adelanta el problema que busca resolver: la separación de las operaciones de lectura de las de escritura. Esta diferenciación, aunque muchas veces ignorada en aplicaciones monolíticas tradicionales, representa uno de los avances más relevantes a la hora de romper el monolito… o incluso de seguir utilizándolo —y aquí podríamos abrir un gran debate—.

De hecho, esta separación no implica necesariamente que debamos adoptar microservicios desde el primer momento. Existen escenarios en los que, mediante una correcta apificación, podemos gestionar componentes o dominios como si fueran microservicios, sin que estén obligatoriamente contenerizados. No hay que confundir microservicios con contenedores. Es cierto que contenerizar servicios es una buena práctica y aporta beneficios claros, pero no es un requisito para aplicar CQRS ni para implementar una arquitectura desacoplada de forma efectiva. Este tema, sin duda, merece una entrada aparte.

Volviendo al enfoque CQRS, aún hoy en día nos encontramos con desarrollos que optan por la llamada “opción de fuerza bruta”: se utiliza una base de datos como Azure SQL con capacidades de escalado automático, o bien se planifican esos picos de escalado en épocas de alta demanda. Esta solución puede parecer viable, pero no es la más eficiente ni la más escalable a largo plazo.

Pensemos en una situación común: una aplicación que ejecuta consultas de lectura intensiva y operaciones de escritura complejas. ¿Por qué deberíamos aceptar tiempos de respuesta elevados en consultas que solo necesitan acceder a datos, cuando lo verdaderamente costoso en términos de recursos es la escritura? Incluso si asumimos que nuestra Azure SQL cuenta con una capacidad considerable, ¿por qué no reservar esa “fuerza bruta” exclusivamente para las operaciones de escritura, y delegar las consultas a una base optimizada para lectura?

Aquí es donde CQRS cobra todo su sentido. Separar comandos (escrituras) de queries (lecturas) permite no solo optimizar recursos, sino también escalar de forma independiente ambas operaciones, aplicar diferentes modelos de datos según el caso (modelo de escritura vs. modelo de lectura) y, sobre todo, pensar en nuestras aplicaciones desde una óptica más desacoplada, mantenible y preparada para el crecimiento.

Patrón CQRS

Aquí es donde cobra especial importancia el patrón CQRS (Command Query Responsibility Segregation). Este patrón consiste en segregar las operaciones de lectura (Queries) de las de escritura (Commands), utilizando eventos y servicios que respeten el principio de responsabilidad única (Single Responsibility Principle).

Gracias a esta separación de responsabilidades, podemos discernir claramente entre qué operaciones afectan al estado del sistema (escritura) y cuáles simplemente consultan datos (lectura). Esto nos brinda una ventaja significativa: la posibilidad de usar distintos modelos y repositorios de datos optimizados para cada tipo de operación, incluso dentro de un mismo microservicio.

En la demo que vamos a presentar, optaremos por una arquitectura sencilla pero potente:

  • Lectura: Usaremos Redis, una base de datos NoSQL in-memory extremadamente rápida, que correrá dockerizada en nuestro entorno local.
  • Escritura: Persistiremos los datos en Azure SQL, aprovechando su robustez, integridad transaccional y familiaridad en entornos empresariales.

Llegados a este punto surge una pregunta crítica: ¿Cómo se entera Redis de que hemos realizado una operación de escritura en Azure SQL?

Aquí entra en juego la verdadera magia del patrón CQRS con eventos. Cada operación de escritura se manejará como un comando, y su resultado generará un evento que podrá ser consumido por otros servicios —en este caso, el encargado de actualizar la vista de lectura en Redis—.

Además, es esencial garantizar el orden y la coherencia de estos eventos. Por ejemplo:

Update employee SET name = 'Jorge' Where id = 2
Delete from employee Where id = 2

Ambas operaciones deben quedar registradas como comandos secuenciales. ¿Por qué? Porque la vista de lectura en Redis debe reflejar fielmente la historia del dato. Si primero actualizamos un empleado y luego lo eliminamos, Redis debe saber que ese empleado ya no existe, incluso si alguna consulta rápida intenta acceder a su información tras el update.

Este enfoque no busca una consistencia fuerte inmediata, sino una consistencia eventual, en la que los cambios propagados a Redis pueden llegar con una leve demora, pero garantizan integridad y trazabilidad del dato. Así, Redis se convierte en una capa de lectura ágil, actualizada a través de eventos derivados de las escrituras que ocurren en Azure SQL.

¿Cómo hacemos eso?

Obviamente esos estados deberiamos guardarlos en algun repositorio que no forme un cuello de botella, si los guardamos en AzureSQL no estamos dando un tiro mortal, ya que para algo estamos usando CQRS, y volveriamos a tener problemas de rendimineto a la la larga, hay otras opciones incluso peores:

  • Table Storage (Muy barata, pero muy lenta)
  • AzureSQL (Precio medio, rendimiento medio)
  • EventSourcing (En nuestro caso EventStorageDB, rapidisima y dockerizable)

Event Sourcing es un patrón arquitectónico donde el estado de una entidad se reconstruye reproduciendo una secuencia de eventos pasados. Cada evento representa un cambio de estado, como OrderPlaced, ProductAddedToCart, PaymentConfirmed, etc.

Algunos ejemplos de Event Sourcing son: EventStorageDB, Kafka, CosmosDB, MongoDB (Estos ultimos con limitaciones).

En nuestro caso optamos por EventStorage DB (también conocido como Event Store o Event Sourcing Database) es un tipo de base de datos diseñada para almacenar los eventos que representan los cambios de estado de una aplicación, en lugar de guardar directamente el estado actual de los datos. Usando EventoStorageDB, de manera asincrona se irán escribiendo las modificaciones de estado sobre nuestro Redis con lo que tendremos operaciones de lecturas alineadas y con toda la potencia que nos da esta herramienta.

La estructa de nuestro código para de una ASP.NET API donde le hemos añadido como «extra» Aspire, para poder gestioner nuestros servicios.

Estructura de la demo

Aplicando el patrón CQRS montaremos sobre las «Applications» nuetras operaciones de escritura (Commands) y los diferentes Handlers para que puedan ser invocadas.

En la capa de «Domain», definiremos las operaciones que negocio que necesitamos crear, en este caso tendremos un UserAggregate + un evento que nos indicará cuando se agregado un user, este será en nuestra demo la única via de escritura.

En nuestra capa de Infrastucture daremos soporte a la conectividad con elementos que nos dan «servicio» es decir Redis, EventStoreDB y AzureSQL (no implementada en esta demo).

EventStoreDb Service
Redis Service

Quedando nuestro servicio tal que las peticiones de lecturas que entran por nuestro Repositorio apuntando a Redis nos darán toda la velocidad necesaria en el patrón, y la peticiones de escritura dispararan un evento dentro de nuestra capa de comandos que haría la inserción en AzureSQL, pero que a su vez dejaría tambien el evento en nuestro _eventStore (EventStoreDB).

RegisterUserHandler

Quedando nuestras peticiones expuesta tal que así:

Peticiones de lectura y escritura via API

Por ultimo y para un correcto funcionamiento debemos de tener en cuenta la manera de codificar nuestro program.cs

Program.cs

Debemos tener en cuenta varios puntos:

  • Al ultizar docker para nuestra imagenes de eventStoreDB y Redis, no podemos usar localhost, porque para esas imagenes localhost seria el propio host del container, en su lugar debemos usar los namespace de las imagenes (eventstore, redis) con sus correspondientes puertos.
  • Se ha forzado el uso de http, ya que las imagenes carecen de certificados por defecto, y nuestra API sería incapaz de conectar con los servicios via https.

Os dejo el enlace al código, y hasta la próxima.

CQRS.EventStore in Github

Tagged in :

Avatar de Jorge Fernández

Deja una respuesta

Otras lecturas que no te puedes perder