You are on page 1of 10

Aclarando CQRS (traduccin)

(Traduccin del artculo de Udi Dahan - Clarified CQRS http://www.udidahan.com/2009/12/09/ clarified-cqrs/)

Despus de ver cmo la comunidad ha interpretado CQRS (Separacin de Responsabilidades con Comandos y Consultas) creo que ha llegado la hora de hacer algunas aclaraciones. Algunos lo han relacionado con Event Sourcing. La mayora han superpuesto sus ideas previas de arquitecturas de capas sobre el nuevo patrn. Aqu espero identificar CQRS en s mismo, y describir en qu puntos puede conectarse con otros patrones.

Por qu CQRS
Antes de describir los detalles de CQRS necesitamos entender sus dos principales potencias conductoras: la colaboracin y la obsolencia. Colaboracin se refiere a circunstancias bajo las cuales mltiples actores usarn o modificarn los mismos datos (tengan la intencin de colaborar entre ellos o no). A menudo hay reglas que indican qu usuario puede realizar qu tipo de modificacin y modificaciones que podran ser aceptables en un caso pueden no serlo en otros. Expondremos algunos ejemplos en breve. Los actores pueden ser humanos, como los usuarios normales, o automticos, como otro software. Obsolencia se refiere al hecho de que en un entorno colaborativo, una vez que los datos han sido mostrados al usuario esos mismos datos pueden haber sido modificados por otro actor, en otras palabras la informacin puede potencialmente ser obsoleta. Cualquier sistema que haga uso de una cach est sirviendo datos obsoletos, a menudo por una cuestin de eficiencia. Lo que esto significa es que no podemos confiar completamente en las decisiones de nuestros usuarios, ya que podran estar tomadas en base a informacin desactualizada . O sea, el hecho que la informacin es obsoleta no es completamente obvia. Las arquitecturas de capas estndar no tratan explcitamente con ninguno de estos problemas (colaboracin y obsolencia ). Mientras que compartirlo todo en una misma base de datos podra ser un paso en la direccin correcta para tratar la Colaboracin, la obsolescencia es normalmente ms acusado en esas arquitecturas por el uso de cachs cuya intencin es mejorar el rendimiento.

Una imagen como referencia

He dado algunas charlas sobre CQRS usando este diagrama para explicarlo:

Las cajas llamadas AC son Componentes Autnomos. Describiremos qu los hace autnomos cuando discutamos los comandos. Pero antes de entrar en lo complicado, vamos a empezar por las consultas (Query en el grfico).

Consultas
Si los datos que vamos a mostrar a los usuarios estarn obsoletos de todas maneras, es realmente necesario ir a la base de datos maestra para obtenerlos de all? Por qu transformar esas estructuras en 3 forma normal a objetos del dominio si nosotros slo queremos datos, y no comportamientos para cumplir las reglas? Por qu transformar esos objetos del dominio en DTOs (Objetos de Transferencia de Datos) para enviarlos a travs de una red, y quin dice que: esa red va a estar exactamente ah? Por qu transformar esos DTOs en modelos para la vista (view models). Resumiendo, parece que estuviramos haciendo demasiado trabajo innecesario basado en el supuesto de que reutilizar cdigo que ya ha sido escrito ser ms fcil que simplemente resolver el problema que tenemos entre manos. As que vamos a probar otra alternativa. Qu tal si creamos un almacn de datos adicional cuyos datos puedan estar un poco desactualizados respecto a la base de datos maestra? Es decir, los datos que mostramos al usuario van a ser obsoletos de una forma u otra, as que por qu no reflejar esta obsolescencia

en el propio almacn de datos. Ms adelante veremos una aproximacin para mantener este almacn de datos ms o menos sincronizado. Ahora bien, cul debera ser la estructura correcta para este almacn de datos? Qu tal el propio modelo de la vista? Una tabla para cada vista. Entonces nuestro cliente podra hacer simplemente SELECT * FROM MiTablaDeVista (o posiblemente pasarle un ID en una clusula Where), y enlazar el resultado con la pantalla. Podra ser as de simple. Podras envolverlo con una fina fachada si sientes la necesidad, o con procedimientos almacenados(Stored Procedures) , o usar AutoMapper para que mapee desde un DataReader a tu clase modelo de la vista. El hecho es que la estructura modelo de la vista ya es buena para transmitir, as que no necesitas transformarla en nada ms. Podras incluso considerar mover ese almacn de datos a tu capa web. As tendra tanta seguridad como una cach de memoria en la capa web. Con darle permisos al servidor web para que slo haga SELECT en esas tablas ser suficiente.

Almacn de datos de las consultas


Pero usar una base de datos convencional como almacn de datos al que consultar no es la nica opcin. Considera que el esquema de las consultas es bsicamente idntico al modelo de la vista. No hay relaciones entre las diferentes clases modelo de las vistas, as que no se debera necesitar ninguna relacin entre las tablas en ese almacn de datos. En ese caso necesitas realmente una base de datos relacional? La respuesta es no, pero por razones prcticas y debido a la inercia de las organizacines, probablemente sea tu mejor opcin (por ahora).

Escalando las consultas


Ahora que las consultas son ejecutadas desde un almacn de datos distinto de la base de datos maestra, y no podemos asumir que los datos servidos estn actualizados al 100%, puedes fcilmente aadir ms instancias a esos almacenes sin preocuparte de que no contengan exactamente los mismos datos. El mismo mecanismo que actualiza una instancia puede actualizar muchas instancias, como veremos ms adelante. Esto te ofrece una escalabilidad horizontal barata para tus consultas. Y al no estar haciendo casi ninguna transformacin, la latencia por consulta disminuye tambin. El cdigo sencillo es cdigo rpido.

Modificaciones de datos
Como nuestros usuarios estn tomando decisiones basadas en datos obsoletos, necesitamos ser ms exigentes respecto a qu dejamos pasar. Pongamos un escenario para explicar por qu: Supongamos que tenemos un servicio de atencin telefnica donde un operador est al telfono con un cliente. Este usuario est viendo los detalles del cliente en su pantalla y quiere hacerlo un cliente preferido, adems de modificar su direccin, cambiar su ttulo Srta. por Sra., cambiar su apellido, e indicar que ahora est casada. Lo que el usuario no sabe es que tras abrir la pantalla, un evento procedente del departamento de facturacin indica que este mismo cliente no paga sus facturas: es moroso. En este punto, nuestro usuario confirma sus cambios. Deberamos aceptar esos cambios? Bien, nosotros deberamos aceptar algunos de ellos, pero no el cambio a preferido ya que el cliente es moroso. Pero escribir este tipo de comprobaciones no es fcil: necesitamos comprobar las diferencias entre los datos, inferir qu significan los cambios, cuales estn relacionados entre s (cambio de apellido y ttulo) y cuales no, identificar contra qu datos comprobar (no slo comparando con los datos que el usuario ley, sino con el estado actual de la base de datos) y entonces decidir si rechazar o aceptar. Desafortunadamente para nuestros usuarios, tendemos a rechazarlo todo si alguna parte est mal. As que nuestros usuarios tendrn que actualizar sus pantallas para obtener los datos actualizados, y volver a escribir todos los cambios anteriores, esperando que esta vez no les gritemos por culpa de un conflicto de concurrencia optimista(Optimistic concurrency). Conforme nuestras entidades se hacen ms grandes, con ms campos, tambin hay ms actores trabajando con esas mismas entidades, y mayor es la probabilidad de que alguien toque algn atributo de ellas en un momento dado, incrementando el nmero de conflictos de concurrencia. Si solo hubiera una forma de que nuestros usuarios nos proporcionen sus modificaciones de datos con un correcto nivel de granularidad y clara intencin. En eso precisamente consisten los comandos.

Comandos
Un elemento clave de CQRS es repensar el diseo de la interfaz de usuario para permitirnos capturar la intencin de nuestros usuarios, por ejemplo marcar un cliente como preferido es para el usuario una unidad de trabajo distinta de indicar que el cliente se ha mudado o que se ha casado. Usando un interfaz de usuario tipo Excel para modificar los datos no captura la intencin, como vimos antes. Podramos incluso considerar que el usuario pudiera enviar un nuevo comando aun antes de haber recibido confirmacin del anterior. Podramos tener un pequeo asistente en el lateral mostrando al usuario sus comandos pendientes, retirndolos asncronamente al recibir confirmacin del servidor, o marcndolos con una X si fallan. El usuario podra entonces hacer doble clic sobre la tarea fallida para encontrar informacin sobre qu ha sucedido.

Noten que el cliente enva comandos al servidor, no los publica. La publicacin est reservada para eventos que establecen un hecho: indicar que algo ha sucedido; y que el publicador no se preocupa sobre qu harn los receptores de ese evento al respecto.

Comandos y Validacin
Pensando en qu podra hacer que un comando fallara, un aspecto que surge es la validacin. La validacin es diferente de las reglas de negocio ya que establece una afirmacin independiente del contexto acerca del comando. Un comando es vlido o no lo es. Las reglas de negocio, por otro lado, son dependientes del contexto. En el ejemplo que vimos antes, los datos que nuestro operador guardaba eran vlidos, slo el evento recibido previamente desde facturacin provocaba que el comando fuera rechazado. Si ese evento de facturacin no hubiese llegado, los datos se habran aceptado. Incluso siendo un comando vlido, todava puede haber razones para rechazarlo. De hecho, la validacin puede ser realizada en el cliente, comprobando que se han rellenado todos los campos requeridos para ese comando, que los rangos de nmeros y fecha se cumplen y ese tipo de cosas. El servidor todava debera validar todos los comandos recibidos y no confiar en que los clientes hagan la validacin.

Replanteo de UI(Interfaz de Usuario) y comandos a la luz de la validacin


El cliente puede usar el almacn de datos para consultas al validar comandos. Por ejemplo, antes de enviar el comando de que el cliente se ha mudado, podra comprobar que el nombre de la calle existe en el almacn para consultas. En este punto, podramos replantear el interfaz de usuario (UI) y poner una caja de texto con autocompletado para el nombre de la calle, asegurando as que la calle que pasaremos en el comando ser vlida. Pero podramos ir an ms all. Por qu no pasar el ID de la calle en lugar de su nombre? Hagamos que el comando represente la calle no como una cadena sino como un ID (int, guid, lo que se quiera). En el servidor, la nica razn de que tal comando falle sera debido a un problema de concurrencia: que alguien haya eliminado esa calle y que todava no se haya aplicado esa eliminacin en el almacn para consultas, un conjunto bastante excepcional de circunstancias.

Razones para que comandos vlidos fallen y qu hacer al respecto


As tenemos un cliente que se porta bien, que est enviando comandos vlidos, y un servidor que todava puede decidir si rechazarlos. A menudo las circunstancias de rechazo tienen que ver con los cambios de estado realizados por otros actores, que resultan relevantes en el procesamento de ese comando. En el ejemplo anterior de CRM, se deba slo a que el evento de facturacin llegara primero.

Pero primero podra ser un milisegundo antes que nuestro comando. Qu pasara si nuestro usuario presionara el botn un milisegundo antes? Debera eso realmente cambiar los resultados desde el punto de vista del negocio? No esperaramos que nuestro sistema se comportase de la misma forma cuando lo observamos desde afuera? As que, si el evento de facturacin llegara el segundo, no se debera volver a poner el cliente preferido como normal? Y ms an: no se debera notificar al cliente, por ejemplo envindole un email? En ese caso, por qu no establecer ese comportamiento incluso cuando el evento de facturacin llegue primero? Y ya que tenemos configurado un modelo de notificaciones, necesitamos realmente devolver un error al operador? Es decir, dado que l no puede hacer otra cosa ms que notificarlo al cliente. Por lo cual si no estamos devolviendo errores al cliente (quien ya nos est enviando comandos vlidos), quiz todo lo que necesitamos hacer en el cliente cuando enviamos un comando es decir al usuario gracias, recibir confirmacin va email en breve. Ni siquiera necesitamos un asistente en el UI para mostrarnos los comandos pendientes.

Comandos y Autonoma
Podemos ver que en este modelo los comandos no necesitan ser procesados inmediatamente, pueden ser encolados. Cuando se ejecuten realmente es cuestin de un acuerdo de servicio (Service-Level Agreement, SLA) y no es significativo desde el punto de vista de la arquitectura. Esta es una de las cosas que hace que el nodo que procesa los comandos sea autnomo desde la perspectiva del tiempo de ejecucin: no se necesita una conexin siempre activa con el cliente. Es ms, no necesitaramos acceder al almacn para consultas para procesar comandos, cualquier estado necesario sera gestionado por el componente autnomo, eso es parte del significado de autonoma. Otra cuestin es el caso de fallos en el procesamiento de mensajes por culpa de cadas de la base de datos o por conflictos bloqueantes. No hay razn para devolver esos errores al cliente, podemos deshacer (rollback) e intentarlo de nuevo. Cuando el administrador recupere la base de datos, todos los mensajes en cola de espera sern procesados con xito y nuestros usuarios recibirn confirmacin. El sistema como un todo es bastante ms robusto ante las condiciones de error. Dado que ya no se lanzan consultas contra esta base de datos, es capaz de mantener ms filas/pginas en memoria para servir comandos, mejorando el rendimiento. Cuando tanto los comandos como las consultas eran servidos por las mismas tablas, el servidor de base de datos estaba siempre haciendo malabares con las filas de ambos.

Componentes Autnomos
En la figura superior vemos todos los comandos dirigidos hacia el mismo AC (componente autnomo), pero podramos lgicamente procesar cada comando en un AC separado, con su propia cola. Esto nos permite visualizar qu cola es ms larga, lo cual nos muestra de una forma muy obvia qu parte del sistema es un cuello de botella. Esta informacin, que es interesante para el desarrollador, es indispensable para los administradores de sistemas.

Dado que los comandos esperan en colas, podemos agregar ms nodos procesadores a esas colas (si estamos usando NServiceBus, podemos emplear el distributor), para de esta manera escalar slo la parte del sistema que es lenta. No hay necesidad de gastar servidores en las otras peticiones.

Capas de servicio
Nuestros objetos de procesamiento de comandos en los distintos componentes autnomos realmente conforman nuestra capa de servicio. La razn de que no se represente explcitamente esta capa en CQRS es porque realmente no est ah, al menos no como una coleccin lgica identificable de objetos relacionados, y he aqu porque: En la aproximacin de la arquitectura en n capas (o 3-capas), no hay reglas en cuanto a las dependencias entre los objetos dentro de una capa, o ms bien se supone que estn permitidas. Sin embargo, al mirar a la capa de servicio a travs del prisma de la orientacin a comandos, lo que vemos son objetos manejando diferentes tipos de comandos. Cada comando es independiente de los otros, as que por qu deberamos permitir que los objetos que los manejan dependan unos de otros? Las dependencias son cosas que deberan ser evitadas, a menos que haya una buena razn para ellas. Mantener independientes entre s estos objetos de manejo de comandos nos permitir evolucionar nuestro sistema ms fcilmente, un comando cada vez, no necesitando ni siquiera detener el sistema completo, dado que la nueva versin es compatible hacia atrs con la anterior. Por lo tanto, hay que mantener cada manejador de comando en su propio proyecto VS, o quizs incluso en su propia solucin, alejando as a los desarrolladores de la tentacin de introducir dependencias en nombre de la reutilizacin (es una falacia). Si despus decidieras, como un aspecto del despliegue, que quieres poner todos juntos en el mismo proceso alimentndose de la misma cola, puedes combinar con ILMerge esos ensamblados y alojarlos juntos, pero entendiendo que puedes estar perdiendo muchos de los beneficios de tus componentes autnomos.

Qu hay del modelo del dominio?


Aunque en el diagrama anterior podas ver el modelo del dominio bajo los componentes autnomos de procesamiento de comandos, realmente slo es un detalle de implementacin. No hay nada que establezca que todos los comandos deben ser procesados por el mismo modelo del dominio. Podra decirse que puedes tener algunos comandos procesados con secuencias transaccionales (transaction scripts), otros usando modulo de tabla (table module o active record), as como otros usando el modelo del dominio. Event sourcing sera otra posible implementacin. Otra cosa a entender referente al modelo del dominio es que ya no se usa para dar servicio a las consultas. As que la cuestin es: por qu necesitas tener tantas relaciones entre entidades en tu modelo del dominio?

(Es posible que desee disponer de un momento para asimilar esto.) Realmente necesitamos una coleccin de rdenes en la entidad cliente? En qu comando necesitaramos navegar por esa coleccin? De hecho, qu tipo de comando necesitara una relacin uno-a-muchos? Y si es as para las uno-a-muchos, las muchos-a-muchos definitivamente estaran fuera tambin. Es decir, la mayora de los comandos slo contienen uno o dos ID en el mejor de los casos. Cualquier operacin de agregacin, que podra haber sido calculada iterando por las entidades hijas, podr ser precalculada y almacenada como propiedades en la entidad padre. Siguiendo este proceso a travs de todas las entidades en nuestro dominio dar lugar a entidades aisladas necesitando nada ms que un par de propiedades para los ID de sus entidades relacionadas (hijos conteniendo el ID del padre, como en las bases de datos). De esta forma, los comandos podran ser completamente procesados por una sola entidad: viola, una entidad raz que es un lmite de consistencia.

Persistencia para el procesamiento de comandos


Ya que la base de datos usada para procesamiento de comandos no se usa para consultas, y que la mayora de (si no todos) los comandos contienen el ID de las filas a las que van a afectar, realmente necesitamos tener una columna para cada propiedad de objetos del dominio? No podramos serializar la entidad del dominio y ponerla en una sola columna, y tener otra columna conteniendo el ID? Esto suena bastante parecido a un almacenamiento basado en claves tal como est disponible en los varios proveedores en la nube. En tal caso, necesitas realmente un mapeador objeto-relacional (ORM) para persistir en este tipo de almacenamiento? Tambin podras extraer una propiedad adicional para cada dato del que quieras que la base de datos asegure la unicidad. No estoy sugiriendo que hagas esto en todos los casos, ms bien trato de hacer que te replantees algunas presunciones bsicas.

Permteme repetirlo
La forma en que se procesan los comandos es un detalle de la implementacin de CQRS.

Manteniendo el almacn para consultas actualizado


Despus de que el componente autnomo de procesamiento de comandos ha decidido aceptar uno, y tras modificar su almacn de persistencia en consecuencia, publicar un evento notificndolo al resto del mundo.Este evento a menudo es la forma pasada del comando: HacerClientePreferidoCommand -> ClienteHaSidoHechoPreferidoEvent La publicacin del evento se hace en la misma transaccin en que se procesa el comando y se hacen los cambios en la base de datos. De este modo, cualquier tipo de error al confirmar

la transaccin (commit) dar lugar a que el evento no sea enviado. Esto es algo que debera ser gestionado por defecto por el bus de mensajes, y si se utiliza MSMQ como transporte subyacente, requiere del uso de colas transaccionales. El componente autnomo que recibe esos eventos y actualiza el almacn de datos para consultas es bastante simple, traduciendo de la estructura del evento a la estructura de persistencia del modelo de la vista. Sugiero tener un manejador de eventos por cada clase modelo de la vista (es decir, por tabla). Aqu tenemos la imagen con todas las piezas de nuevo:

Contextos limitados
Aunque CQRS afecta a muchas piezas de la arquitectura del software, todava no est en la cima de la cadena alimenticia. CQRS, si se usa, ser aplicado dentro de un contexto limitado (DDD) o de un componente de negocio (SOA): una pieza cohesiva del dominio del problema. Los eventos publicados por un BC sern escuchados por otros BC, y cada uno de ellos actualizar sus almacenes (de comandos y de consultas) como sea necesario. Los interfaces de usuario de CQRS que encontramos en cada BC pueden ser unificados en una nica aplicacin, ofreciendo a los usuarios una nica vista compuesta con todas las partes del dominio del problema. Las libreras de composicin de UI sern de gran ayuda para estos casos.

Resumen
CQRS versa sobre cmo alcanzar una arquitectura apropiada para aplicaciones multiusuario colaborativas. Explcitamente considera factores como la obsolescencia de datos y la

volatilidad, y explota esas caractersticas para crear construcciones ms simples y escalables. Uno no puede disfrutar verdaderamente los beneficios de CQRS sin considerar el interfaz de usuario, sin hacer que este capture explcitamente la intencin del usuario. Cuando se considera la validacin en el lado del cliente, la estructura de comandos debe ser de alguna forma reajustada. Pensando ms profundamente en el orden en que los comandos y eventos son procesados puede conducir a patrones de notificacin que hagan innecesaria la devolucin de errores. Mientras que el resultado de aplicar CQRS a un proyecto dado es una base de cdigo ms facil de mantener y eficiente, esta simplicidad y escalabilidad requieren de la comprensin detallada de los requisitos del negocio y no son el resultado de ninguna buena prctica de carcter tcnico. Si acaso, podemos ver una pltora de enfoques a problemas aparentemente similares siendo empleados conjuntamente: lectura con cursores frente a modelos de dominio, mensajes unidireccionales frente a llamadas asncronas. Aunque este artculo supera las 3000 palabras (un rcord para este blog), reconozco que no profundiza lo suficiente en la materia (toma unos 3 das de los 5 de mi curso Advanced Distributed Systems Design cubrirlo todo en suficiente detalle). En cualquier caso, espero que te ofrezca un entendimiento de por qu CQRS es como es y quiz te abra los ojos a otras formas de mirar al diseo de sistemas distribuidos. Las preguntas y los comentarios sern muy bienvenidos. [Off]

Glosario
CQRS (Separacin de Responsabilidades con Comandos y Consultas) Event Sourcing (Fuentes de Eventos, Eventos como Fuente) Sin traducir Staleless (Estancamiento de datos, obsolencia) Data store (Almacn de datos) Query data store (almacn de datos para consultas) View model (Modelo de la vista) UI widget (asistente) hitting a deadlock (conflictos bloqueantes) service layer (Capa de servicio)

You might also like