A la vuelta de las vacaciones me han entrado ganas de escribir algo. Sin ganas de hacer el típico post retrospectivo del SpainJS, y por petición popular, he decidido escribir sobre ese gran desconocido que es CQRS. Como no quiero que vuestros ojos se fatiguen mi plan es hacer varios posts cortos en vez de uno con la longitud acostumbrada. En éste me centraré en hacer una introducción al tema. Ya nos meteremos un poco más en detalle en los siguientes.
En una arquitectura tradicional, tenemos un único sistema que se encarga de realizar operaciones de negocio y nos permite consultar la información en la que se encuentra nuestro sistema. Sin embargo, esto viola el principio de única responsabilidad (SRP), ya que el mismo sistema se encarga de hacer dos cosas que en principio son muy distintas: exponer operaciones que evolucionen el estado del sistema de forma consistente, y leer el estado del sistema. Estas dos responsabilidades tienen requisitos muy diferentes desde distintos puntos de vista: funcional, escalabilidad, tiempo de respuesta, seguridad, criticidad, etc. Si nos tomamos en serio el principio de separación de responsabilidades, y somos partidarios de arquitecturas modulares en vez de sistemas monolíticos y centralizados, debemos buscar una arquitectura alternativa.
Command Query Responsability Segregation (CQRS), es un estilo arquitectónico en el que tenemos dos subsistemas diferenciados, uno responsable de los comandos, y otro responsable de las consultas. Por comando entendemos un petición por parte del usuario u otro sistema, para realizar una operación de negocio, que evolucione el sistema de un estado a otro. Cada uno de estos subsistemas tiene un diseño, modelo de información y mecanismo de persistencia diferente, optimizado para las tareas que deba afrontar. Normalmente el subsistema de consulta suele ser mucho más simple que el otro. Veamos la siguiente figura para explicar un poco como funciona esto (se nota que soy un artista):
El subsistema de comandos, simplemente recibe peticiones de comandos, valida que éstos son consistentes con el estado actual del sistema, y si es así los ejecuta. Como resultado de la ejecución de un comando, el estado del sistema cambia, y ese cambio se comunica al subsistema de consultas mediante algún mecanismo de sincronización.
El subsistema de consulta recibe los cambios en el estado del sistema mediante el mecanismo de sincronización. Durante la etapa de filtrado ignora los cambios en los que no está interesado. En algunos mecanismo de sincronización esta etapa puede formar parte de la configuración del mismo, y no del código de aplicación. Después los cambios se pueden pasar por una etapa opcional donde se pueden transformar, añadirle información calculada y agregar información varios cambios. De nuevo en algunos casos el mismo mecanismo de sincronización podría proporcionarnos herramientas para definir esta etapa de forma declarativa. Finalmente se actualiza de forma adecuada la base de datos. La ejecución de la consulta simplemente consiste en exportar los datos de la BBDD como DTOs y serializarlos en un formato adecuado.
Si en el subsistema de consulta usamos una BBDD relacional, podríamos definir un esquema optimizado para las consultas, de forma que estas no necesiten joins, y las sentencias SELECT sean muy simples y sencillas de optimizar. Tal vez una tabla por consulta, con una columna por parámetro de consulta, y una columna adicional con el resultado en formato ya directamente serializado. Evidentemente otras opciones son posibles.
Y ya está, cualquier sistema que cumpla lo descrito se puede considerar CQRS. Ahora bien, como el demonio está en los detalles, existen muchos puntos importantes a decidir:
- La naturaleza exacta del mecanismo de sincronización entre el subsistema de comandos y el de consultas. Se puede usar algo tan simple como una llamada local intraproceso, o algo más sofisticado como un sistema de colas, o un servicio web (REST o SOAP). También hay que decidir si la sincronización será pull o push, es decir, ¿el subsistema de comandos notificará al de consulta los cambios? ¿O tal vez el subsistema de consultas preguntará de forma periódica que cambios se produjeron desde la última vez?
- El paradigma de persistencia de cada subsistema. Puede ser SQL tradicional, o noSQL. Lo interesante es que cada subsistema puede usar el paradigma que más le interese. En concreto si usamos relacional para las consultas, se nos plantea la disyuntiva de usar un esquema desnormalizado totalmente optimizado para las consultas (una tabla por consulta con las columnas específicas a recuperar por ésta) o bien un esquema en tercera forma normal que nos de más flexibilidad.
- Si el subsistema de comandos tiene persistencia o no. En realidad el subsistema de comandos sólo necesita consultar el estado para saber si puede ejecutar un comando o no. Esta consulta podría proceder del subsistema de consulta, en vez de una BBDD propia. Esto puede ser una opción según que modelo de consistencia sea admisible.
- Transaccionalidad en el subsistema de comandos. ¿Cuándo se da por terminada la transacción representada por el comando? ¿Cuándo el nuevo estado del sistema se ha modificado en memoria? ¿O tal vez, cuando este estado se ha persistido en la BBDD específica del subsistema de comandos? ¿Quizás sería mejor esperar a que el subsistema de consulta haya recibido el cambio y actualizado el modelo de consulta? Esta decisión es crítica a la hora de definir el modelo de consistencia de datos que queremos (ACID o BASE).
- ¿Un sólo subsistema de consulta o varios? La arquitectura CQRS nos permite definir varios subsistemas de consultas, especializados en cosas diferentes. Cada uno puede tener un enfoque diferente en cuanto a la persistencia y consistencia. Por ejemplo uno podría usar un sistema noSQL para consultas específicas, y otro podría ser un sistema OLAP para hacer minería de datos.
- ¿Un sólo subsistema de comandos o varios? De nuevo CQRS nos da la libertad de tener distintos subsistemas especializados en distintas familias de comandos. Uno para procesar comandos relacionados con los pedidos, otros para el control del stock, otro para pagos.
Muchas decisiones, muchas combinaciones, cada una de ellas con su correspondientes ventajas e inconvenientes. Los siguientes posts de la serie tratarán de explorar un poco estos aspectos.
El usar CQRS nos da los siguientes beneficios:
- Ambos subsistemas pueden evolucionar por separado; el mantenimiento y despliegue puede estar diferenciado. Esto es una ventaja porque normalmente el ritmo de cambios en la funcionalidad es muy diferente en las consultas y los comandos. En mi experiencia es más frecuente añadir una nueva consulta que un nuevo comando.
- Ambos sistemas pueden escalar por separado. Si nuestro negocio es muy intensivo en lecturas, podemos dedicar más máquinas al subsistema de consultas de forma sencilla. Simplemente levanta otra instancia y conéctala de forma que pueda recibir los cambios transmitidos por el subsistema de comandos.
- El estilo CQRS es modular de forma inherente. Podemos escoger ir a una arquitectura modular, donde haya varios subsistemas de consulta y varios subsistemas de comandos. Cada uno de ellos de nuevo puede ser diseñado, mantenido, escalado y desplegado por separado. Es muy interesante la posibilidad de añadir nueva funcionalidad (nuevos subsistemas de comandos o consultas) sin tener que parar el resto del sistema.
Sin embargo todo esto no es gratis:
- Es más complejo, sobre todo debido a que debemos diseñar con cuidado el mecanismo de sincronización en los comandos y las consultas.
- El tener sistemas de persistencia separados para los comandos y las consultas, o el tener múltiples subsistemas de cada uno, puede provocar que tengamos cierta duplicación de información. Esto puede ser negativos si estamos preocupados por ahorrar espacio en disco.
- Para construir el subsistema de comandos necesitamos saber que comandos existen, como evolucionan el estado del sistema y cuando la ejecución de un comando es consistente con el estado del sistema. Todo esto exige modelar bien el negocio, olvidarse de las tablas y centrarse bien en las acciones del usuario y modelar bien los casos de uso como máquinas de estado o similar. O sea, no sirve para hacer aplicaciones de mantenimientos de tablas o CRUD. El problema es que muchos frameworks de moda (ROO, GRAILS, RAILS…) están optimizados para ser productivos haciendo CRUD, no para CQRS. No digo que sea imposible usar, por ejemplo RAILS, pero sí es verdad que no va a ser tan productivo.
Los dos últimos puntos son dificultades relativas. El tener duplicación de información y distintos subsistemas es bueno desde el punto de vista de la redundancia y la disponibilidad de la información. Si un subsistema de consultas se corrompe, se puede reconstruir a partir del subsistema de comandos. Por otro lado el no poder usar un enfoque CRUD, o que los frameworks de moda no nos faciliten tanto la tarea, lo veo más un problema social que de ingeniería.
¡ Anda, creo que me ha quedado un post corto !
Muy buen artículo, espero el siguiente 🙂
Algunos recursos que creo están relacionados:
http://martinfowler.com/bliki/CQRS.html
Hola.
Creo que estás mezclando conceptos que no son intrínsecos de CQRS. CQRS no es un estilo de arquitectura, sino un simple patrón. No implica que haya que usar dos sistemas independientes de almacenaje para almacenar la información, ni event sourcing, envío asíncrono de mensajes, etc, que me da que es por donde van el resto de tus posts. 😉
Te recomiendo que leas:
http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/
http://lostechies.com/jimmybogard/2012/08/22/busting-some-cqrs-myths/
Alberto, como creo que explico en el post, CQRS no es una arquitectura, sino un estilo arquitectónico. O si prefieres un patrón de arquitectura. Hay muchas formas de «instanciarlo» en función de las tecnologías y decisiones de diseño que tomes. Pero no merece la pena discutir sobre temas de nomenclatura. El propio Greg Young al que enlazas dice lo mismo: «In other words the interesting stuff is not really the CQRS pattern itself but in the architectural decisions that can be made around it…»
Cuando hablo de CQRS no estoy pensando en CQS, que son dos cosas totalmente distintas, como bien indica el señor Young. En ninguna parte del post aparece nada de diseño OO. Tampoco hablo de UIs, ni de tasks based UIs.
Puedes hacer CQRS sin Event Sourcing, y también puedes hacer CQRS sin mensajes asíncronos, y puedes hacer CQRS y usar el estilo de UI que quieras, no tiene porque ser task based. Todo tiene sus pros y sus contras. De hecho en este post quiero dejar claro que no son lo mismo y usar CQRS no implica usar todo esto por fuerza. Eso sí, en la mayoría de los escenarios combinar CQRS, EventSourcing, task based UIs y demás es la decisión más acertada (y en eso sí que podemos estar en desacuerdo). Y sí, defenderé esta última frase en el resto de los posts de la serie donde hablaré como encajan el CQRS, el EventSourcing, DDD, etc.
Lo que ocurre, y creo que de ahi viene la confusión, es que todos estos conceptos están relacionados, y cuando instancias CQRS es muy natural combinarlos porque encajan perfectamente. No puedes decir que CQRS es un patrón de diseño, y no comentar como se relaciona (fuertemente) con otros patrones y técnicas, y que opciones hay de implementarlo, al menos la gente del GoF no lo hacía así. Hay que tener visión de conjunto y apreciar como se interrelacionan distintos conceptos aparentemente muy dispares.
Mi exposición a CQRS viene del señor Martin Fowler, http://martinfowler.com/bliki/CQRS.html, y también de Udi Dahan, http://www.udidahan.com/category/cqrs/, que por cierto, te recomiendo encarecidamente leer.
Salud !
P.S. Los artículos que mencionas me parecen bastante buenos
Gracias por el post Enrique,
Pienso que algun lector podria tener la tendencia de separar query de command hasta el punto de que una responsabilidad estaria repartida entre varias clases. Es decir, lo normal es que la gente escriba artefactos, mas concretamente clases, con varias responsabilidades en lugar de una sola. Pero a veces ocurre lo contrario, que una responsabilidad esta desperdigada por varias clases. Entiendo que el patron CQRS no restringe que una misma clase pueda tener un metodo de query y otro de command. Por ejemplo un servicio. El negocio no entiende la separacion entre consulta y comando, asi que si vamos desarrollando basandonos en ejemplos de comportamiento puntuales, no parece muy natural la emergencia del CQRS.
Me gusta mucho el patron y lo uso donde creo que encaja, pero plantearlo como arquitectura a priori me pareceria peligroso.
Debo copiarle el gesto a Carlos y agradecerte el artículo. Sin duda es una magnífica introducción a un tema que ciertamente desconocía.
Sin embargo veo un par de puntos discutibles:
En principio, un sistema tradicional que permite tanto lecturas como escrituras no viola el SRP. Es fácil llegar a la conclusión de que cuando una «cosa» (sistema, módulo, función…) efectúa (o está hecha de) múltiples «cosas», entonces rompe los principios del diseño correcto de software con los que todos estamos familiarizados.
Sin embargo, la cardinalidad no es el factor determinante a la hora de evaluar un diseño como simple (bien!) o complejo (mal!). Lo que realmente importa es si, dado un diseño, se pierde la identidad de sus componentes o no. En el primer caso estaremos mezclando, y en el segundo, componiendo.
Así que, no veo como «conceptual» el motivo por el que uno debería adoptar un diseño CQRS: más bien es una optimización ante altos niveles de carga. Con múltiples costes, notablemente el de la transaccionalidad. Quizá esto tiene una importancia más relativa en servicios «informativos» como Google o Twitter, pero para otros sistemas sencillamente no hay elección.
También veo múltiples huecos que se han de implementar cada vez, tal como mencionas (el mecanismo de «sincronización» siendo el más notable). Así que entiendo el CQRS no como una simplificación (al contrario que muchas otras abstracciones, que sí lo son), sino más bien un compromiso importante que no puede tomarse a la ligera.
Por último, personalmente ya no me fio de lo de «simplemente levantar otra instancia» y afirmaciones similares que uno puede encontrar en todo tipo de contextos (e.g. Node.js, NoSQL), en otros artículos. There’s no silver bullet 🙂
Hola Vemv, es cierto, decidirse por CQRS es una decisión a valorar con tranquilidad y que conlleva muchas consecuencias. Pero lo mismo se puede decir de cualquier arquitectura. A menudo me encuentro que la mayoría de nosotros simplemente nos decidimos por «la arquitectura de siempre», sin ni siquiera valorar si es la más adecuada o no. Total, es «la arquitectura de siempre», seguro que es la mejor, tal y como nos han enseñado nuestros mayores, no hace falta plantearse nada ni tomar decisiones difíciles. Como ingeniero a mi este modo de pensamiento mágico y costumbrista me resulta insoportable. Uno de los objetivos de mis posts es que la gente sepa qué es CQRS y lo tenga en cuenta a la hora de elegir la arquitectura del sistema que estén desarrollando.
Yo personalmente llegué a CQRS desde un punto de vista conceptual, no de rendimiento. Gestionar las consultas y realizar operaciones de negocio (comandos) son dos cosas totalmente diferentes, que funcionalmente evolucionan a ritmos diferentes y tienen requerimientos no funcionales distintos. Eso sí, están interrelacionados, por eso los califico de subsistemas y no de sistemas independientes.
Sobre lo de «levantar otra instancia», el tema merece su propio post que espero escribir. Obviamente como dices no es trivial, pero sí te digo que es más sencillo que con otras arquitecturas.
Y por supuesto llevas razón, CQRS no es mágico. Si lo implementas mal te puedes meter en un lío muy gordo. Y en algunos casos no es la mejor opción.
Salud, y bienvenido a mi blog!
[…] Comentarios « CQRS (1): ¿Qué es? […]
Buenas Enrique,
Interesante artículo e interesantes comentarios.
Por mi parte llevo algunas semanas investignado CQRS y cada vez le veo más sentido. La verdad es que el uso de, por ejemplo, una base de datos operacional y otra de consulta es algo muy habitual, y nada nuevo, en muchos entornos; y veo CQRS como una reafirmación y evolución de esta idea.
Ciertamente el patrón de uso de los métodos de captura de datos y consulta suelen ser muy diferentes, y las consecuencias pueden justificar el modelado de la solución en la forma de dos sistemas, tal como expones.
Por otra parte, creo que estoy de acuerdo con vemv en cuanto a que no lo veo como una cuestión conceptual de modelado OO, y comparto que podría resultarme difícil justificar CQRS sin tener en cuenta criterios de optimización de carga y escalabilidad.
[…] ¿Qué es CQRS? Sincronización Event Sourcing […]
Interesante! Espero aprender mas de sus post. Estoy estudiando la carrera en Análisis de Sistemas, y aunque no sé cuál es la relación verdadera entre mi carrera y Ingeniería de Software, me vienen bien estos post para sumergirme en este mundo.
Saludos!