¿Qué es CQRS y cómo puede mejorar tu arquitectura de software?
En el mundo del desarrollo de software, siempre buscamos herramientas y técnicas que nos ayuden a manejar de manera más eficiente nuestros sistemas y aplicaciones. Aquí es donde entra CQRS (Command Query Responsibility Segregation), una técnica que puede cambiar por completo la manera en que manejamos nuestras operaciones de lectura y escritura dentro de una aplicación.
¿Cómo funciona CQRS?
CQRS, o Segregación de Responsabilidades de Comandos y Consultas, se basa en la idea de separar las operaciones que alteran el estado del sistema de aquellas que simplemente consultan datos. Esto se traduce en dos componentes principales:
Comandos: Operaciones de escritura que cambian el estado de algún objeto o sistema. Por ejemplo, guardar un usuario o modificar un atributo de una base de datos.
Consultas: Operaciones de lectura que se encargan de recuperar información, como "dame todos los usuarios registrados en los últimos 30 días" o "obtén la fecha de la última venta".
Al separar estos componentes, logramos una estructura más eficiente y sencilla de mantener, especialmente en aplicaciones que necesitan escalar.
¿Por qué elegir CQRS?
Hay diversas razones para adoptar CQRS:
Escalabilidad: Algunas aplicaciones pueden requerir una administración más intensiva de las operaciones de lectura o escritura. Separando estas operaciones, cada componente puede escalar independientemente según las necesidades del sistema.
Mantenimiento: La separación de responsabilidades generalmente lleva a un código más limpio y comprensible, facilitando el mantenimiento de la aplicación a largo plazo.
Desempeño: Permite optimizar componentes específicos de acuerdo a su función principal (leer o escribir), mejorando el rendimiento general.
Ejemplo de implementación en C#
CQRS se podría implementar en C# utilizando una estructura de proyecto predefinida que enfatice las arquitecturas limpias. Por ejemplo:
publicclassCreateTodoListCommand:IRequest{publicstring Title {get;set;}}// Handler para el comando CreateTodoListpublicclassCreateTodoListCommandHandler:IRequestHandler<CreateTodoListCommand, Result>{privatereadonlyTodoDbContext _context;publicCreateTodoListCommandHandler(TodoDbContext context){ _context = context;}publicasyncTask<Result>Handle(CreateTodoListCommand request,CancellationToken cancellationToken){var todoList =newTodoList{ Title = request.Title }; _context.TodoLists.Add(todoList);await _context.SaveChangesAsync(cancellationToken);return Result.Success(todoList.Id);}}
En este ejemplo, el comando CreateTodoListCommand está diseñado específicamente para manejar la operación de creación de una lista de tareas, utilizando Entity Framework para interactuar con la base de datos.
¿Qué sucede con las consultas?
Las consultas se manejan de manera similar, pero se enfocan únicamente en recuperar datos:
Este patrón asegura que el código esté limpio y que cada clase tenga una sola responsabilidad, mejorando la mantenibilidad y legibilidad del código.
¿Cómo consumir los comandos y consultas?
En una arquitectura que adopta CQRS, los controladores (en una API, por ejemplo) no interactúan directamente con las consultas o comandos. En lugar de eso, utilizan un mediador que maneja los eventos y consultas:
[HttpGet]publicasyncTask<ActionResult<IList<TodoList>>>GetTodos(){var query =newGetTodosQuery();var result =await _mediator.Send(query);returnOk(result);}
El uso de un framework como Mediator permite lanzar eventos para manejar consultas y comandos, promoviendo un desacoplamiento significativo entre las capas de la aplicación.
¿Dónde más se puede aplicar CQRS?
Además de operaciones dentro del código, CQRS se puede expandir a:
Métodos: Evitando mezclar operaciones de lectura/escritura en un único método.
Clases: Separando las clases en función de si gestionan comandos o consultas.
Servicios y backends: Implementando microservicios o backend dedicados para operaciones de lectura y otro para operaciones de escritura.
En escenarios más avanzados, incluso se puede aplicar CQRS a nivel de bases de datos, separando bases de datos dedicadas a operaciones de lectura y escritura, lo cual sería beneficioso en entornos de alta concurrencia.
Implementar CQRS es un paso importante hacia una arquitectura de software más robusta y escalable. Con un poco de práctica, verás cómo esta técnica puede transformar tu forma de desarrollar aplicaciones. ¡Anímate a investigar más sobre este tema y sigue aprendiendo!
Creo que la mejor manera de explicar este curso es con un proyecto de ejemplo e irlo desarrollando paso a paso.
Considero que lo mas importante es no confundir a CQRS con la separacion de base de datos de escritura y lectura eso es mas bien lo que puede hacer el repository o un sistema de replica de base datos. Si no mas bien que separa la escritura de la lectura y esto es muy importante ya que en la escritura usualmente consumimos casos de uso y el dominio por lo cual hay validaciones en la lectura es util que no existan validaciones porque algunas veces por ejemplo se cambia a una arquitectura limpia pero se debe mantener el código legacy por tanto puede que exista data "sucia"
Por que en el ejemplo el comando esta retornando el tipo ActionResult<int> no deberia ser void ya que los comandos no retornan nada?
Esta es una duda frecuente con los comandos en CQRS. Sin embargo, el punto crítico aquí es que no hagan consultas.
Para facilitar el desarrollo con CQRS, es válido regresar información que nos ayude a saber el resultado del comando (en este caso, el ID del objeto que se creó).
Este es un artículo muy interesante si deseas profundizar en este y otros temas adicionales de CQRS.
CQRS
Command Query Reponsability Segregation
Se divide en comandos y consultas.
Comandos
Escritura.
Cambian el estado.
Consultas
Lectura.
Retornan resultados.
Esta idea se puede aplicar a varios niveles:
Métodos.
Clases.
Servicios.
Bases de datos.
Para cuando el curso que no sea un "abre bocas introductorio"?
**Carpeta "src": **Esta carpeta contiene el código fuente principal del proyecto.
**Carpeta "Application": **Contiene la lógica de aplicación y los casos de uso del sistema.
Carpeta "Domain": Contiene las entidades, objetos de valor y reglas de dominio del sistema.
Carpeta "Infrastructure": Contiene la implementación de infraestructura y servicios externos.
Carpeta "Persistence": Contiene la lógica de persistencia y acceso a datos.
**Archivo "Colour.cs": **Este archivo, ubicado en la carpeta "Domain/ValueObjects", representa la clase Colour que define un objeto de valor para representar colores en el dominio del sistema. El archivo define métodos y propiedades relacionados con los colores, como la creación de instancias de colores, conversiones implícitas y explícitas a cadenas, y la lista de colores compatibles. También se definen constantes para colores comunes como blanco, rojo, naranja, amarillo, verde, azul, morado y gris.
En un sistema legacy muy transaccional, donde se tienen tablas maestras (tablas que acumulan saldos) de las cuales se consulta con frecuencia para generar reportes o consultas para hacer validaciones que lo vuelven con un alto nivel de concurrencia y bloqueos en la base de datos, es justo donde CQRS puede ser útil, pero cómo implementarlo para evitar esos bloqueos a la base de datos?
Hablemos del modelo de las consultas y su relación con SQL. Si tienes un reporte en el que tienes que utilizar muchas tablas relacionadas y muchos campos calculados basado en valores de varias tablas. ¿En dónde es mejor hacer la lógica de la consulta? ¿En SQL? ¿Llevar las consultas simples con el ORM a una clase de aplicación y allí realizar la lógica para así también mantener controlada las reglas del negocio?
Hola, Mauricio. Para reportes complejos que involucran múltiples tablas y cálculos, la respuesta depende de tus prioridades de rendimiento y mantenimiento:
SQL (Vistas o Procedimientos): Es ideal si el volumen de datos es masivo. SQL está optimizado para realizar uniones (joins) y agregaciones de forma eficiente. Delegar el cálculo a la base de datos reduce drásticamente la carga de memoria en tu aplicación.
Capa de Aplicación (ORM): Es preferible si la lógica de negocio es altamente cambiante o compleja de expresar en SQL. Esto mantiene las reglas centralizadas en tu código, facilitando las pruebas unitarias.
Recomendación: Si el reporte es solo de lectura, considera crear una Vista en SQL o una consulta optimizada. Esto respeta el principio de segregación de CQRS: separar la lectura eficiente de la lógica de escritura.
CQRS se puede aplicar a métodos, clases, servicios y bases de datos. La idea acá es que dependiendo el caso tu puedes separar tus consultas o lógica de consultas con las de escritura, me gustó mucho la idea de tener backends separados por el tema de concurrencia, nunca lo había pensado, la de DB de escritura y lectura si.
Es una buena practica tener un método en el servicio que sea findOrCreateSomething()? Entiendo que quizás no en el modelo pero pienso que en el servicio si.
CQRS: Segregación de responsabilidades de comandos y consultas
comandos: escritura en DB, alguna acción que modifique los datos en la DB
consultas: queries, lectura de la DB, retorna resultados.
Cuando utilizamos CQRS tenemos 2 modelos, uno que se encarga de las consultas y otro de los comandos, esto se hace porque alguna de ellas puede ser mas cambiante y sirve para optimizar el código y que sea mas entendible, este más organizado.
En resumen, CQRS mejora la escalabilidad, el rendimiento y la mantenibilidad de una aplicación. Esto se debe a que los sistemas tienen diferentes requisitos para manejar comandos y consultas, y al segregarlos, se puede optimizar cada operación de manera independiente.
Comandos (Commands): Son de tipo escritura, ya que accionan modificando datos, como crear, actualizar o eliminar información.
Consultas (Queries): Son de tipo lectura, ya que están optimizadas para proporcionar respuestas rápidas a las solicitudes de datos.