miércoles, 18 de mayo de 2011

Segregacion de Responsabilidades de Comando y Consulta (CQRS)


Command and Query Responsability Segregation (CQRS)
Fue casi un año desde que lei el libro azul (Domain Driven Design; Tackling complexity in the Heart of Software). Cambio mi visión de como escribo el software y como construir arquitectura del software. Durante el tiempo de desarrollador, intente muchos enfoques distintos en la creación del software. Un montón de enfoques incluyen un modelo de dominio anémico. No hay nada malo en construir un modelo de dominio anémico. Un modelo de dominio anémico es un término utilizado que describe el uso de un modelo de dominio de software donde la lógica de negocio esta implementada fuera de los objetos de dominio. La lógica esta típicamente implementada en clases separadas que transforman el estado de los objetos de dominio. Este patrón es un enfoque común en aplicaciones empresariales .NET siguiendo la arquitectura de aplicaciones de servicios de tres capas, donde los objetos caen dentro de la categoría de “Business Entities” entidades de negocios (aunque entidades de negocios puede también contener comportamientos).
Para aplicaciones con lógica de negocios mas compleja, puede que no sea la mejor opción. Terminamos con un montón de lo que se dice código espagueti con alto nivel de acoplamiento entre las distintas partes del código. Modelos de dominio anémicos tienen su lógica de entidades distribuidas por todo el código y generalmente tenemos que actualizar varias partes en el código si una regla de negocio cambia. Tengamos esto  en mente cuando codificamos.
Siempre codifiquemos como si el tipo que termina manteniendo el código será un psicópata violento que sabe dónde vivimos.
Un modelo de dominio rico clásico tiene toda la lógica de negocio dentro del modelo y la mayoría de los objetos están conectados unos con otros. Se esfuerza por ser un modelo que resuelve la lógica del dominio para el negocio en un modelo perfecto. Y es aquí donde este modo de escribir software falla. Obtenemos un gran modelo que finalmente se pone muy pesado.
 Divide el modelo
En lugar de tratar de resolver todo en un modelo, divídelo en pequeñas partes y trabaja con cada porte. Crea agregados que naturalmente encajen juntos. Un ejemplo puede ser un auto y un cliente. En este ejemplo, ambos son agregados raíces. Debería considerar no modelar ambos objetos y sus agregados en un modelo. Pensemos en ellos como pequeños modelos separados dentro del modelo. Esto hará el modelo mucho mas fácil de manejar cuando persistimos o cargamos en memoria.
El problema con bases de datos relacionales
En estos días muchos usamos ORMS para persistir datos de un modelo de dominio en la base de datos, y utilizan una base de datos relacional como base de datos. El problema con las bases de datos relacionales es que tenemos que hacer un compromiso con la velocidad de lectura y la velocidad de escritura. Si normalizamos la base de datos, será mejor para los casos como insertar, actualizar, y eliminar pero no leyendo datos. Los problemas son los índices. Ellos en la mayoría de los casos bajaran el rendimiento de las operaciones de selección de datos, pero ayudan en operaciones para insertar, eliminar y actualizar. Tenemos que hacer un compromiso, leer rápido o escribir rápido.
Si decidimos hacer la base de datos mejor para la lectura de datos (select), una opción es de- normalizar la base de datos, Una base de datos de-normalizada significa tablas con datos repetitivos y posiblemente tablas con un gran número de columnas.
Como nosotros desarrolladores no damos mucha importancia a esta pregunta, como escalar la solución. Creo que tenemos que tener este pensamiento en nuestra cabeza cuando creamos una solución que puede servir muchos usuarios.
Command and Query Responsability Segregation.
La mayoría de las aplicaciones lee datos mas frecuentemente que cuando escriben. Basados en esto, sería una buena idea hacer una solución donde podemos agregar más bases de datos para la lectura, ok? ¿Entonces si establecemos una base de datos dedicada a la lectura? ¿Aun mejor, que pasa si diseñamos una base de datos de modos que sea rápida para la lectura? Si diseñamos nuestra aplicación basadas en CQRS, tendremos una solución es escalable para la lectura de datos. Command Query Separation es un patrón que fue introducido por Bertrand Meyer.


Un rápido paso a paso por la arquitectura CQRS


Una vista grafica de la arquitectura. Un usuario abre una aplicación y la primera pantalla se carga. Los datos son lanzados desde el lado consulta. En este ejemplo va a través de un servicio WCF y utiliza, digamos NHibernate para cargar datos desde la base de datos y dentro del DTO que retorna los datos a la GUI. El DTO esta modelado para que encaje en la pantalla que esta visualizando el usuario. La base de datos de consulta es a menudo de-normalizada para mejorar las lecturas. El usuario puede navegar por las distintas pantallas, y el proceso es el mismo. Un DTO modelado para la pantalla del usuario es devuelto por la base de datos. Eventualmente, el usuario quiere cambiar los datos en una de las pantallas. Lo que sucede es que un mensaje del lado Command es creado basado en los datos que han cambiado en la vista y envidaos por el lado izquierdo de la figura, el lado Command. El mensaje comand es enviado dentro del modelo de dominio para ser validado contra las reglas de negocio. Si el mensaje pasa a través del modelo sin errores, es salvado en la base de datos de escritura y sincronizado con la base(s) de lectura. El lado de Command nunca debe retorna otro dato que no sea un mensaje de error. Si seguimos esta regla, el lado del comando será solo comportamental. Esto hace muy fácil loggear que sucede en el modelo de dominio, y es fácil seguir lo que quiso hacer el usuario, no solo cuales fueron los resultados de sus acciones. Este enfoque puede ser también utilizado en una aplicación ya existente (Brown field) sin mucho cambio de la arquitectura, solo tenemos que agregar el lado de consulta a la arquitectura vieja.
¿Porque agregar DTOS del lado de la consulta?
En un modelo de dominio tradicional, crearemos objetos para resolver lógica del dominio, no para visualización. Tienen comportamiento en lugar que forma. Para hacer los objetos de dominio mas visualizables, muchos desarrolladores utilizan mapeos entre el modelo de dominio y los DTOs modelados para visualización. Esto resulta en una gran cantidad de mapeos entre objetos para el desarrollador y modelos del dominio que exponen getters y setters.
Si estamos utilizando ORMs como NHibernate, tenemos que agregar getters al modelo de dominio y yo pienso que asi esta bien. El modelo esta aun protegido contra cambios no deseados que puede hacerlo invalido. Cada cambio debe suceder a través de los métodos Command.

1 comentario:

  1. Este documento se basa en documentacion existente, ahora publicada por mi en español.

    ResponderEliminar