Feeds:
Entradas
Comentarios

Archive for the ‘cqrs’ Category


¡ Bienvenidos de nuevo al mundo de CQRS ! Hoy nos vamos a centrar en ver como podemos implementar la persistencia del subsistema de comandos. La opción más tradicional es persistir una instantánea del modelo de datos directamente en una base de datos relacional mediante un ORM o similar. Los amantes de noSQL querrán grabar dicha instantánea usando sus sistema noSQL favorito. Pero ambas opciones se basan en grabar una instantánea del estado del modelo de datos. Sin embargo existe un sistema más simple y que aprovecha el ya existente mecanismo de eventos. Me estoy refiriendo a Event Sourcing.

Concepto y motivación

Ya hemos decidido que el subsistema de comandos va a emitir eventos de negocio, lo cuales serán procesados por el subsistema de consultas para actualizar su esquema de datos interno. Esto tiene algunas consecuencias cruciales:

  1. El subsistema de comandos no va a ser nunca leído por los clientes del sistema, ya que ellos usan siempre el subsistema de consulta.
  2. El subsistema de consultas es el único cliente que realiza operaciones de lectura sobre el subsistema de comandos.
  3. Lo único que va a pedir el subsistema de consultas al de comandos es la lista de eventos de negocio ocurridos desde la última vez que se produjo una sincronización.
  4. Si ocurre un desastre y perdemos todos los datos del subsistema de consultas, basta con levantarlo sin datos, y hacer que le pida al subsistema de comandos todos los eventos ocurridos desde el principio. Por lo tanto el estado del sistema puede residir por entero en el subsistema de comandos. Más sobre esto en futuros posts.

Visto esto, ¿para qué vamos a molestarnos en persistir un esquema de datos basado en entidades y relaciones? Al fin y al cabo ni los clientes ni el subsistema de consulta están interesados en estas entidades. Además lo único que necesitamos para recuperar el sistema en caso de desastre es la lista de eventos de negocio. Por lo tanto lo único que debería ser persistente es la lista de dichos eventos y no un hipotético modelo entidad/relación o un modelo de objetos.

Y este es precisamente el famoso Event Sourcing: almacenar todo el estado del sistema únicamente como una secuencia ordenada de eventos. Por lo tanto si nos decidimos por un mecanismo de sincronización basado en eventos de negocio, el enfoque de Event Sourcing a la persistencia es muy natural. No necesitamos almacenar entidades, ni las relaciones entre ellas, sino serializar una lista de eventos. Cada evento puede poseer campos, pero nunca vamos a necesitar buscar eventos por dichos campos, sino sólo por orden temporal, o como mucho por tipo de evento. El subsistema de consultas sólo va a realizar accesos como el que sigue: «dame todos los eventos sobre pedidos desde el momento X hasta la actualidad por orden cronológico», o «todos los eventos desde el principio».

Otra ventaja de event sourcing es que tenemos todo el historial de operaciones del sistema, lo que lo hace ideal para auditorias, depuración e informática forense.

No digo que debamos usar siempre event sourcing, pero siempre y cuando usemos eventos de negocio como mecanismo de sincronización, me cuesta trabajo pensar en escenarios donde event sourcing no tenga ventajas sobre grabar el modelo tal cual.

Transaccionalidad y consistencia

A nivel transaccional es bastante sencillo: cada comando es una transacción, cada transacción termina con la generación de un evento. A nivel de consistencia podemos tener varios niveles, en función de cuando el comando (la transacción) se da por terminado:

  • Fin de transacción cuando el evento se genera en memoria. Tanto la persistencia del evento como su transmisión al subsistema de consultas se produce de forma asíncrona, en segundo plano. Esto nos da el máximo de escalabilidad y rendimiento. Pero la consistencia es eventual y  podemos perder eventos (durabilidad baja).
  • Fin de transacción cuando el evento se persiste. La sincronización se produce de forma asíncrona, en segundo plano. De esta forma no perdemos eventos, a costa de tener una consistencia eventual entre las consultas y los comandos. Sin embargo la escalabilidad es bastante buena, a condición de que las escrituras sean rápidas. Es un buen compromiso como norma general.
  • Fin de transacción cuando el evento se persiste, y además el subsistema de consultas ha sido actualizado. Aquí tenemos consistencia estricta y durabilidad, pero penalizamos el rendimiento de las escrituras. Si por cualquier caso el subsistema de consulta está muy estresado, podemos perder disponibilidad en la escritura. Si realmente te encuentras en un escenario que requiere una consistencia tan estricta como esta, tal vez no deberías estar usando ni event sourcing ni CQRS.

Como vemos debemos usar un mecanismo de persistencia que escale muy bien a nivel de escritura (excepto en el primer caso) para que nuestro subsistema de comandos escale. No es tan importante en este caso la escalabilidad a nivel de lectura, ya que de ello se encarga el subsistema de consultas, que puede tener otro mecanismo de persistencia totalmente distinto. Además nos basta con un mecanismo que permita escribir en modo «append only» y soporte consultas cronológicas y por tipo. ¿Cómo implementamos pues la persistencia?

Alternativas de implementación

Podemos, como siempre, usar una BBDD relacional como soporte sobre el que almacenar la secuencia de eventos. Pero aunque factible, no es muy ventajoso. Al fin y al cabo nunca vamos a consultar por columnas, o por clave primaria. Tampoco vamos a tener que hacer joins. Ojo, no digo que no podamos usar SQL, lo que quiero hacer notar es que en este escenario el paradigma relacional no nos ofrece muchas ventajas.

Parece mejor usar un paradigma más sencillo. Por ejemplo, en un clave/valor podríamos usar como clave el número de orden del evento y como valor el propio evento serializado directamente. Una simple consulta por rango de clave nos valdría. Otra opción es usar algo como REDIS, que tiene soporte directo para listas y operaciones atómicas de incremento. Por otro lado, algunos sistemas noSQL, como Cassandra optimizan la escritura sobre la lectura, lo que viene bien en el caso de event sourcing.

Podemos simplificar aun más, y usar directamente el sistema de ficheros. Si lo pensáis bien en Event Sourcing no hay ni modificación ni borrado. Toda modificación en el sistema ocurre a través de un comando, que como resultado cambia el estado de este. Como consecuencia del cambio de estado siempre se produce un nuevo evento, que habremos de añadir a la secuencia de eventos ya existente. Lo mismo ocurre con las operaciones de borrado. A nivel de negocio raramente ocurre un borrado como tal. Podemos cancelar un pedido, o anularlo, o sea ejecutar otro comando que termina produciendo otro evento. Como se ve no se necesita ni modificar ni borrar ningún evento. Esto es importante porque tanto los discos magnéticos como los SSD se comportan bastante bien con sistemas que sólo añaden información por el final. En teoría no debería ser muy complicado hacer un sistema que abra un fichero en modo append only… qué curioso acabo de resucitar el concepto de log transaccional o log de operaciones, que se ha usado durante décadas en sistemas transaccionales robustos. Pero meterse a niveles tan bajos sólo compensa si realmente necesitas mucho rendimiento.

Ya existen sistema de persistencia especializados para event sourcing. Son los llamados Event Storages. Sin embargo todos los que conozco no acceden directamente al sistema de ficheros, sino que mapean el concepto de event sourcing sobre otros motores de persistencia como mySQL, Redis o Cassandra.

Snapshoting

Sin embargo la técnica de event sourcing tiene dos debilidades: ficheros que siempre crecen y arranques de sistema lentos.

Como vimos, no es común hacer borrados de los eventos ya que casi todas las operaciones terminan en un evento nuevo que hay que añadir al sistema de ficheros. Esto hace que el espacio de almacenamiento requerido crezca sin parar. Podemos definir eventos de negocio que ocupen muy poco espacio, y usar técnicas de compresión, pero eso sólo hace que el espacio ocupado crezca más lentamente.

Por otro lado, cada vez que el sistema se para y tiene que ser reiniciado, debe leer todo el log de eventos y reprocesarlos para obtener una instancia del modelo de negocio en memoria. Esto hace que el tiempo de arranque sea largo. Este problema afecta al subsistema de comandos principalmente. El subsistema de consultas suele almacenar directamente el estado del modelo de consultas, y sólo necesita procesar los eventos desde la última vez que se paró. Por lo tanto este problema no es importante en el caso del subsistema de consultas.

En los casos en que estos dos problemas son importantes, no se usa event sourcing puro, sino una técnica híbrida llamada snapshoting. La idea es usar event sourcing, pero en segundo plano, y cada cierto tiempo, ir procesando los eventos para generar una instantánea del estado del sistema que será persistida. Esta instantánea estará desfasada con respecto a la secuencia de comandos. La idea es almacenar tanto el log de eventos como la instantánea, por lo que se puede considerar event sourcing puro. Las ventajas son las siguientes:

  • Evitamos volver a procesar el log de eventos completo en caso de arranque. Cuando el sistema arranca lee la instantánea, y procesa sólo los eventos que ocurrieron posteriormente a la creación de dicha instantánea.
  • Se pueden borrar los eventos anteriores a la creación de la instantánea, a condición de que se pueda generar una secuencia de eventos equivalente a ella a partir de la instantánea.

Conclusión

Aprovechando que estamos usando eventos de negocio para sincronizar ambos subsistemas, y usar Event Sourcing como paradigma de persistencia en sistemas CQRS. Este enfoque se integra de forma natural con CQRS, es sencillo de mantener y evolucionar, y en general es una buena base para conseguir sistemas escalables. A nivel de rendimiento es importante que nuestro mecanismo de persistencia sea capaz de escalar bien en escritura.

En el próximo post nos adentraremos en el mundo del subsistema de consultas.

Read Full Post »

CQRS (2): Sincronización


En el anterior post sobre CQRS vimos que existían muchas formas de implementar este estilo arquitectónico, ya que había que tomar varias decisiones de diseño, principalmente que paradigma de persistencia íbamos a usar en el subsistema de comandos y en el de consultas, el método de sincronización entre éstos y el nivel de consistencia que nos interesa. En este post nos centraremos en la sincronización.

¿Qué sincronizamos?

Dejando de lado la tecnología concreta que pudiéramos usar para sincronizar ambos subsistemas, nos surge una pregunta interesante: cuando decimos que ambos subsistemas se sincronizan, ¿cual es exactamente la información que se intercambia entre ambos? ¿Qué es lo que enviamos y recibimos a través de ese mecanismo de sincronización? Si no queremos darle muchas vueltas, podemos simplemente hacer que el nuevo estado, resultante de una operación de negocio, sea recibido por el subsistema de consulta. Es una solución sencilla y directa, pero tiene sus problemas:

  • Transmitir el estado completo una y otra vez puede ser ineficiente.
  • Una de las ventajas de CQRS es precisamente que ambos susbsistemas pueden tener modelos de información diferentes, uno optimizado para operaciones y otro para consultas. Si transmitimos el estado del modelo del subsistema de comandos, la transformación de este en el modelo de información de consulta puede ser costosa e implicar una lógica compleja.

Otro enfoque al problema de la sincronización es transmitir sólo los cambios en el estado del sistema, y no el nuevo estado al completo. De esta forma nos evitamos enviar bastante información, con lo que solucionamos el primer problema. Ummm, dos subsistemas que se sincronizan enviando únicamente los cambios de estado, esto suena sospechosamente a eventos. Técnicamente sólo serían eventos si decidimos que nuestra sincronización siga un paradigma «push» (publish/subscribe). Pero alternativamente podríamos usar un paradigma «pull», donde el subsistema de consulta pregunta al de comandos qué cambios se produjeron desde un determinado momento. En este caso alguno podría argumentar que no son estrictamente «eventos» sino «cambios». Yo, por simplicidad, hablaré siempre de eventos, tanto en el caso de «pull» como de «push».

Ya estoy viendo que a más de uno le ha hecho «click» en la cabeza, y ha visto la luz: «claro, ahora si cambio la propiedad en una entidad, emito un evento «property change», y si creo una instancia emito un evento de «create», con lo cual usando el «framework X» lo tengo resuelto y …» Yo mismo he pensado esto en su momento, pero claramente este enfoque nos lleva al lado oscuro del CRUD, que siempre está ahí para tentarnos. A este enfoque lo llamo usar eventos CRUD, y no lo recomiendo en general para CQRS. El problema es que el subsistema de consulta debe ser capaz de interpretar los eventos de la forma más desacoplada posible del subsistema de comandos, pero los eventos CRUD están definidos en función del esquema de datos del subsistema del comandos. Si al subsistema de consultas le llega un evento «property change de la propiedad P sobre la entidad E», éste debe conocer el esquema de datos del subsistema de comandos para poder interpretarlo. Si hiciéramos un cambio en dicho esquema, tendríamos seguramente que modificar el código en el subsistema de consulta, aunque no se haya producido ninguna modificación en la lógica de negocio. Es más, es probable que haya eventos que ya no tengan sentido, al desaparecer alguna entidad o propiedad. Lo que se intenta con CQRS es que ambos subsistemas puedan evolucionar por separado, y para ello necesitamos que el acoplamiento entre ambos sea bajo. Por lo tanto evitad los eventos CRUD, excepto en el caso que tengáis una necesidad real del mismo esquema de datos en ambos subsistemas (con lo que CQRS no sería una solución tan ventajosa).

Lo que necesitamos son eventos de negocio, que representen cambios en el estado del modelo de negocio, no en el esquema de datos. Para averiguar que eventos de negocio tenemos, debemos hacer antes un buen análisis de la funcionalidad de negocio. Esto es consistente con CQRS, ya que de todas maneras debemos averiguar que operaciones de negocio (comandos) tenemos, por que estados pasa el sistema, etc. Por ejemplo, en una tienda electrónica debemos modelar el ciclo de vida de un pedido. Podemos llegar a algo como esto:

A simplified order lifecycle

A simplified order lifecycle

Existiría un comando «Checkout» que una vez ejecutado por el subsistema de comandos, podría hacer evolucionar el estado del pedido desde «Open» a «Accepted«, o en el caso de un problema, tal vez de pago, al estado «Rejected«. Estas transiciones de estado generarían respectivamente los eventos de negocio «OrderAccepted» y «OrderError«. Ambos eventos transportarían el identificador de pedido y quizás algún campo auxiliar, como por ejemplo, una razón para el rechazo del pedido. Obviamente hay bastantes comandos, estados y eventos, y seguramente falten cosas, pero el hacer un pedido implica un proceso de negocio no trivial.

De esta forma, el acoplamiento entre ambos subsistemas se basa sólo en el formato de los eventos, y en compartir un modelo de negocio común, pero el subsistema de consultas no tiene porque saber como se han estructurado las tablas o los objetos en el subsistema de comandos. El ciclo de vida de un pedido va a cambiar sólo cuando cambie el negocio, no por cualquier detalle técnico, por lo tanto constituye un contrato más estable que un esquema de datos.

El lector avezado habrá notado que esto está muy alineado con el enfoque Domain Driven Design (DDD). Operaciones de negocio, eventos de negocio, estados por los que evoluciona un pedido, etc. Todo esto está muy relacionado con el concepto de lenguaje ubicuo de DDD, no es de extrañar que CQRS sea una arquitectura a la que se suele llegar aplicando la metodología DDD, y viceversa, forzándote a hacer CQRS es fácil que termines haciendo DDD.

Conclusión: una forma muy apropiada de sincronizar ambos subsistemas es mediante eventos de negocio.

¿Cómo sincronizamos?

Desde el punto de vista tecnológico, lo primero a decidir es si queremos un mecanismo de notificación «pull» o «push».

El paradigma «pull» se basa en que el cliente pregunta al servidor para obtener los datos. Como estamos hablando de pedir cambios, el cliente debe hacer «polling», es decir repetir la consulta cada cierto tiempo. Esto en algunos casos es ineficiente, ya que muchas veces el cliente va a preguntar en vano, ya que no hay cambios que notificar. De esta manera vamos a estar preguntando y consumiendo recursos para no conseguir nada. Sin embargo en algunos casos es ventajosos usar este paradigma:

  • Es muy simple, no se necesita ningún producto ni protocolo sofisticado para llevarlo a cabo.
  • Internet y la web están diseñados para el paradigma «pull». Por lo tanto están optimizados para este tipo de interacción, en concreto el uso correcto de las caches, las peticiones HTTP condicionales y el protocolo ATOM/RSS, nos permiten implementar este enfoque de forma eficiente.
  • Es muy interoperable.
  • El servidor (en este caso el subsistema de comandos) está totalmente desacoplado de los clientes. No necesita saber cuantos clientes tiene, ni que eventos recibió cada cliente, ni nada. Simplemente responder a las consultas. Es decir podemos implementar un servidor stateless, que es más sencillo y escalable.

Por lo tanto en algunos escenarios es bastante interesante usar este enfoque, por ejemplo:

  • El subsistema de consulta y el de comandos se comunican a través de la web. Tal vez ambos están en diferentes máquinas en la nube (ideal para alta disponibilidad). En este caso se puede usar HTTP con ATOM/RSS para distribuir los eventos mediante una interfaz REST.
  • Una tercera parte ha implementado una aplicación de agregación de información que usa nuestros eventos. Tal vez sea un business partner, otra empresa de nuestro grupo, el departamento del edificio de al lado, o quizás la nuestra sea una startup que quiere ofrecer una API. De nuevo lo lógico es publicarlo mediante una API REST, que es inherentemente «pull».

En general todos estos escenarios implican una alta latencia, lo que provoca que las consultas puedan estar segundos o minutos atrasadas con respecto al estado real del sistema.

El paradigma «push», donde el cliente se subscribe al servidor y recibe los eventos sólo cuando ocurren. El cliente no necesita preguntar al servidor periódicamente, es notificado cuando es necesario. La principal desventaja de este sistema es el acoplamiento entre el servidor y el cliente. El servidor debe mantener una lista de subscripción de forma persistente, y almacenar que eventos fueron enviados a que clientes. Esto puede producir un problema de escalabilidad si tenemos un número ilimitado de clientes. También complica el diseño del subsistema de comandos. En general esto se solventa usando mecanismos de mensajería como JMS o AMQP. También se pueden usar productos de middleware, por ejemplo un producto de Enterprise Service Bus.

Algunos escenarios:

  • El subsistema de consulta y el de comandos están en el mismo proceso. Nos basta con implementar un patrón «Observer».
  • Ambos subsistemas están en la misma máquina o en el mismo CPD. Usaríamos un sistema de mensajería o un Enterprise Service Bus

En resumen, existen a nivel tecnológico muchas variantes y soluciones para implementar el mecanismo de sincronización. Lo importante es que transmitamos eventos de negocio mediante este.

En el próximo post nos adentraremos en el mundo del subsistema de comandos.

Read Full Post »

CQRS (1): ¿Qué es?


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):

CQRS Architecture Overview

CQRS Architecture Overview

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 !

Read Full Post »