Agosto de 2015
Volumen 30, número 8
Tecnología de vanguardia: CQRS y eventos: un dúo eficaz
Por Dino Esposito | Agosto de 2015
Como ocurre con muchos aspectos del software moderno, nada es realmente nuevo. Las cosas a menudo se vuelven a etiquetar y se presentan con palabras nuevas y convincentes. La segregación de responsabilidades de consultas de comandos (CQRS) es un excelente ejemplo. Los eventos de dominio que describen cambios observables en el dominio empresarial son otro.
Al definir el lenguaje de programación Eiffel a finales de la década de los 80, Bertrand Meyer formalizó el principio sobre la base de lo que hoy se denomina CQRS. En software, cualquier acción básica puede ser un comando o una consulta, pero nunca ambas cosas. Si es un comando, se espera que modifique el estado del sistema. Si es una consulta, se espera que informe del estado del sistema sin modificarlo de ninguna manera.
En otras palabras, formular una pregunta no debería cambiar la respuesta. Esto se conoce a veces como el principio de separación de consultas de comandos (CQS). Eso está bien para comandos y consultas, pero ¿qué ocurre con los eventos de dominio?
Eventos en aplicaciones de negocio
Desde los primeros días de la ingeniería de software, los arquitectos han diseñado aplicaciones de línea de negocio capaces de sobrevivir a cambios en el negocio y realizar un seguimiento de esos cambios. Para admitir la inteligencia empresarial y el análisis estadístico, los arquitectos también se las han arreglado para hacer secuencias de acciones repetibles en cierta medida. No los llamaron enseguida "eventos de dominio" o con interesantes términos nuevos como "abastecimiento de eventos"; aún así el concepto de eventos de dominio se ha usado ampliamente durante años en los sistemas empresariales. Esto es especialmente cierto para sistemas bancarios, de finanzas y de contabilidad.
A continuación, tuvimos la revolución del diseño controlado por dominio (DDD) y nos perdimos en este modelo de software universal "nuevo". Creo que todo el mundo perdió cierta perspectiva sobre la arquitectura de software. Los desarrolladores se centraron principalmente en capas y descartaron los segmentos verticales de sistemas como pilas de comandos y consultas.
En mi opinión, la diferencia entre el principio CQS formalizado por Bertrand Meyer y el principio de CQRS actual radica únicamente en donde se aplica. CQS es más que una principio universal para el desarrollo de software. CQRS incide en la arquitectura del sistema. Al aplicar CQS en el nivel de arquitectura, tiene CQRS.
He presentado primero la versión de nivel de entrada de CQRS en el artículo, "CQRS para la aplicación común" (msdn.microsoft.com/magazine/mt147237). Profundicé algo más en el artículo subsiguiente, "CQRS y aplicaciones basadas en mensajes" (msdn.microsoft.com/magazine/mt238399).
En estos artículos se describe la pila de comandos basada en mensajes que se intercambian entre la presentación y lógica de negocios a través de la capa de aplicación. Trataré brevemente los eventos empresariales que puede guardar en un almacén ad hoc para elementos empresariales pertinentes, como un pedido de producto o una cancelación.
El enfoque estaba más en el uso de mensajes para implementar flujos de trabajo de negocio desencadenados por comandos. En ese contexto, los eventos eran simplemente notificaciones entre controladores de apariciones recientes antes las que era posible que los controladores tuvieran que reaccionar. Este es el primer paso de una evolución más larga destinada a la transición de arquitectos de software de la idea de "modelos para persistir" a la idea de "eventos para registrar". Veo CQRS como punto de partida de un cambio que tendrá un efecto profundo en la arquitectura del sistema.
La manzana de Isaac Newton y la mía
Ha oído la historia de la manzana que le cayó a Isaac Newton en la cabeza y que le llevó a formular la Ley Universal de la Gravedad. Probablemente es más una leyenda que algo que pasó realmente pero es cierto que muchos descubrimientos empiezan debido a un curioso acontecimiento. Recientemente tuve mi propia versión de un hecho similar que me hizo pensar seriamente en CQRS y en eventos. Estaba realizando un análisis preliminar para volver a generar un sistema que un cliente ha estado usando durante unos cinco años.
El sistema del cliente se encontraba en un sitio web de ASP.NET MVC 2 con una base de datos de back-end de SQL Server sin formato. La capa de acceso a datos se centraba en una base de datos relacional que se consumía a través de Entity Framework. Las clases de entidad deducidas se ampliaron con métodos adicionales y específicos de negocio mediante el mecanismo de clase parcial de Microsoft .NET Framework. El comportamiento entre entidades se delegó a servicios de dominio. Aunque no lo llamaría una implementación de referencia del patrón de modelo de dominio, tenía partes de hechos de DDD en uno u otro aspecto. Por lo general, yo lo llamaría un sistema normal de crear, leer, actualizar, eliminar (CRUD) con cierta cantidad de lógica de negocios como búfer entre el back-end y el resto del sistema.
En software, un sistema CRUD es una aplicación creada en torno a un grupo de tablas de base de datos. Las capas que rodean a la base de datos validan y sentar las bases para las principales operaciones de CRUD frente a la base de datos. Ese era el sistema.
Otro aspecto de un sistema CRUD que a menudo pasa desapercibido o que simplemente no recibe la atención suficiente es que usa la base de datos como instantánea. En cualquier momento, la base de datos que se encuentra detrás de un sistema CRUD almacena el último estado sistema correcto conocido. A veces, esto resulta suficiente; otras veces, no.
La cabeza que me cayó en la cabeza me hizo darme cuenta de que un enfoque de base de datos de instantánea era correcto para la versión uno. Con la versión dos, el cliente quiere más. Ahora el cliente desea poder realizar un seguimiento del historial y repetir secuencias de pasos en condiciones diferentes para determinar el resultado. En cuanto a esto, la base de datos de instantáneas como la única forma de almacenamiento (o la principal) ya no es adecuada.
Una aplicación de reservas de ejemplo
Considere una aplicación que permite a los usuarios reservar una sala de reuniones. En algún momento, la capa de presentación mostrará una pantalla similar a la Figura 1.
Figura 1 Prototipo de la hoja de horas de salas
Imagine que cada sala tiene sus propias reglas de negocio respecto a las reservas. Por ejemplo, los usuarios pueden reservar la sala blanca durante una hora de 8 a 10 y luego, desde las 13 en adelante. La sala azul tiene intervalos de tiempo de 30 minutos desde las 9:30 de la mañana. Por último, la sala verde está disponible a partir de las 9 a.m. para intervalos de una hora. Estos detalles se guardan y se hacen coincidir con las reservas existentes. Puede generar una cuadrícula donde puede sombrear intervalos ya tomados y dejar disponibles intervalos en los que se puede hacer clic.
Las reglas que rigen la disponibilidad de las salas no se establecen de manera fija y de hecho se pueden cambiar. No es un problema. Si cambia, deje que el administrador edite las reglas en consecuencia. Este enfoque funciona bien siempre y cuando use el prototipo de la Figura 1 para presentar el estado del sistema más reciente.
El momento en que el cliente pide poder navegar entre la cuadrícula actual y la cuadrícula de tres meses antes, está perdido. Más concretamente, está perdido si se realizan cambios en las reglas de negocio de reserva durante las últimas tres semanas. Conoce las horas y los intervalos de tiempo para cada sala, pero ha perdido los detalles de cuáles eran tres semanas antes.
Cuando el cliente planteó esta cuestión, pensé que corregirlo consistiría simplemente en incorporar alguna información que faltaba a la tabla. Todo lo que tengo que hacer es guardar algunas reglas y volver a leerlas cuando las necesite. Tan sencillo como puede parecer, el tratamiento adecuado de la poca información que falta requiere un profundo cambio de la arquitectura. Es como la punta del iceberg, y el iceberg cambia desde una base de datos de instantáneas hasta la representación de datos basada en eventos.
El abastecimiento de eventos hace referencia a la representación de datos basada en eventos. No trataré el abastecimiento de eventos aquí de manera detallada, pero ese es precisamente el tema de la siguiente columna. Para el resto de este artículo, analizaré un enfoque mixto que une instantáneas de base de datos y registros de eventos de negocio. Probablemente es un buen primer paso para realizar la transición de las arquitecturas clásicas existentes hacia el futuro. Más que cualquier nueva tecnología elegante, marco de trabajo, versión y tiempo de ejecución, lo importante de los próximos años estará relacionado principalmente con los datos de eventos de arquitectura como el origen de datos principal en lugar de las instantáneas de datos (como se muestra en la Figura 2).
Figura 2 El iceberg submarino de eventos
Más allá de las instantáneas de datos
Durante décadas, la mayoría de nosotros creamos preocupados únicamente con el estado actual del sistema más reciente. En este escenario, las bases de datos almacenaban instantáneas de entidad (o agregados si se siente más cómodo con la terminología de DDD) y funcionaban perfectamente. Si nunca ha sentido la necesidad de registrar eventos de negocio mientras se producen, probablemente no la tendrá en el futuro.
Sin embargo, estaré perdido en el momento en que el cliente solicite una característica para la que el reloj de la aplicación debe moverse hacia atrás a una hora determinada. La única manera es volver a crear la arquitectura del sistema para guardar eventos de negocio. Supongamos ahora que tiene una tabla similar a la de la Figura 3.
Figura 3 Una tabla de ejemplo que guarda reglas de reserva para salas
Esta tabla indica que puede reservar la sala 1 según las diferentes reglas entre el 1 de marzo y el 1 de junio. También significa que debe volver a diseñar la cuadrícula de la Figura 1 según la fecha a la que hace referencia.
El registro de la tabla RoomRules equivale de manera lógica a un evento de negocio, en el sentido de que indica un evento que se produjo para cambiar la regla de reserva. Sin embargo, en cuanto a la implementación, es un registro simple en una tabla adicional y requiere un montón de consultas adicionales (una por sala) antes de rellenar la cuadrícula de la Figura 1.
Una nueva regla de reserva solo requiere un nuevo registro para la tabla. Sin embargo, los desarrolladores diligentes probablemente ya tendrían algún tipo de tabla de reglas. Proporcionarían una fila por sala. En este contexto, la única diferencia parece ser una columna adicional denominada ValidSince en la Figura 3.
Procedente de una visión clásica de almacenamiento de instantáneas, el uso de "evento empresarial" para definir un conjunto de registros adicionales de una tabla adicional puede parecer excesivo. Aún así, se trata de forma precisa de la perspectiva que obtendrá cuando esté debajo de las instantáneas y con todo el mundo del abastecimiento de eventos justo a sus pies.
En términos más específicos, las reglas para reservar salas son eventos y su registro es el seguimiento del historial del sistema. Obtener las reglas para aplicarlas a una sala determinada es "repetir" los eventos para esa sala que se han producido desde que se inició el sistema y hasta un punto determinado. Puede que no tenga el estado actual guardado en algún lugar, pero tiene todos los eventos registrados. Para obtener el estado del sistema, solo tiene que reproducir los eventos y crear una instancia válida de la entidad de la sala.
¿Suena extraño? Así es cómo cualquier software de banca calcula su "saldo" real. El saldo nunca se obtiene de una lectura sencilla. Lo más probable es que una instantánea persistió en una fecha determinada y se repitió en todos eventos posteriores.
Eventos y CQRS juntos
Los eventos permiten software más allá de la imaginación. Incluso los sistemas CRUD simples se modifican tan pronto como los clientes solicitan formularios simples de inteligencia empresarial y análisis estadísticos. Los eventos son inmutables. Por ese motivo, puede replicar con facilidad las bases de datos de eventos para las nuevas instancias de software. Se trata de una instrucción de clave de escalabilidad. Al mismo tiempo, tener los comandos y las consultas separadas (y así, distintas pilas para procesamiento de comandos y ejecución de consultas) le permiten centrarse en los eventos de la pila de comandos y tener tablas de instantáneas listas para lecturas rápidas en la pila de consultas.
Si tiene problemas de rendimiento, debe guardar los eventos cuando ejecute un comando y actualizar todas las proyecciones de datos que necesita para las consultas. Por ejemplo, al agregar una nueva regla de reserva, esa será partir de ese momento el valor predeterminado. Puede actualizar una tabla adicional de la configuración actual que puede consultar rápidamente sin reproducir eventos. A continuación, puede volver atrás y reproducir eventos según sea necesario.
Resumen
Con los eventos, no se perderá nada. Todavía tiene su negocio ejecutándose y empezar a aprender un tipo de arquitectura más eficaz. Al almacenar datos como eventos, baja un paso en el nivel de abstracción de datos, lo que significa que puede usar la misma cantidad de datos de nivel inferior para crear cualquier número de proyecciones e instantáneas para muchos fines empresariales, como consultas simples, inteligencia empresarial, análisis de hipótesis, estadísticas, análisis de uso y cualquier otra cosa que pueda imaginar.
Dino Esposito es el coautor de “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) y “Programming ASP.NET MVC 5” (Microsoft Press, 2014). Esposito, evangelizador técnico para las plataformas Android y Microsoft .NET Framework en JetBrains y orador frecuente en eventos del sector en todo el mundo, comparte su visión de software en software2cents.wordpress.com y en Twitter en twitter.com/despos.
Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Jon Arne Saeteras