martes, 29 de diciembre de 2009

DDD - Parte 3: Refactorizando Hacia una Visión más Profunda #3

Cuando refactorizamos un modelo hacia una visión más profunda, hay tres recomendaciones que Eric Evans nos hace:
  • Debemos vivir en el dominio
  • Debemos seguir explorando los conceptos de maneras diferentes
  • Debemos mantener un diálogo ininterrumpido con los expertos del dominio
Con este post, planeo cerrar la tercera parte de Domain-Driven Design. No explicaré nuevos conceptos, sino que repasaré los que ya estuvimos viendo en los dos post anteriores.

Inicio

Los móviles para iniciar una refactorización hacia una visión más profunda pueden ser muchos. Probablemente uno de los más importantes sea que el diseño se haya vuelto demasiado complejo y torpe; claramente un problema de este tipo no se soluciona aplicando una transformación mecánica al código. Quizás puedan estar faltando algunos conceptos en el modelo, o ciertas asociaciones puedan ser erróneas. El código y el modelo pueden estar desconectados del lenguaje del experto del dominio, un nuevo requerimiento puede no encajar de ninguna manera en el modelo ya existente, o pudimos haber aprendido más sobre el negocio y haber vislumbrado cómo exponer conceptos fundamentales.

Equipos de Exploración

La búsqueda de un nuevo modelo puede requerir más tiempo y la participación de más gente de lo esperado. Un grupo de cuatro o cinco personas puede reunirse en una sala de conferencia o en un café y hacer una tormenta de ideas (brainstorming) durante una hora u hora y media. Durante el brainstorming se pueden bocetar diagramas UML, diagramas ad-hoc, para buscar un modelo más flexible, que se ajuste mejor a la realidad y a las necesidades del sistema. Cuando el equipo obtenga algo con lo cual evolucionar, regresará al código y codificará las modificaciones. Algunos cambios puede convenir postergarlos para reflexionarlos con mayor detenimiento, otros se pueden implementar inmediatamente. Unos días después, el grupo podrá reunirse de vuelta para repetir el brainstorming, debatir si los cambios fueron apropiados y qué otras cosas se pueden mejorar.

Para que este proceso sea productivo se requiere:
  • Autodeterminación: los equipos pueden armarse sobre la marcha para explorar un problema de diseño particular y disolverse a los pocos días
  • Mantener acotado el Alcance: los aspectos a tratar en las reuniones de los equipos de exploración deben ser acotadas; si el equipo se atasca durante el brainstorming significa que se está intentando abarcar demasiado contenido de una sola vez
  • Ejercitar el Lenguaje Ubicuo: estas reuniones son una oportunidad perfecta para ejercitar el lenguaje ubicuo; el resultado puede conducir a un refinamiento del lenguaje que será reflejado en el modelo y en el código

Estado del Arte

No siempre es necesario reinventar la rueda. Durante el brainstorming podemos obtener ideas de otras fuentes del dominio, como libros, papers, blogs. Alimentarnos del conocimiento ya masticado, ya organizado, probablemente nos guíe hacia un modelo que resulte más familiar al experto de dominio. También los patrones de análisis pueden sernos útil para brindar conceptos sutiles, difíciles de crear, y evitar así muchos errores comunes.

Un Diseño para Desarrolladores

Un software no es sólo para usuarios. También es para desarrolladores. Los programadores deben integrar código con distintas partes del sistema, entenderlo y manipularlo para construir sobre lo ya construido. Un Diseño Flexible comunica sus intenciones, logrando que resulte más sencillo anticipar los efectos de la ejecución del código y, por lo tanto, predecir las consecuencias de los cambios. Un Diseño Flexible limita la sobrecarga mental, reduciendo dependencias y efectos secundarios.

Calendario

Si esperamos a tener una completa justificación para introducir un cambio, significa que hemos esperado demasiado. La Refactorización Continua es considerada como una buena práctica; sin embargo, muchos equipos de trabajo siguen siendo demasiado cautelosos a la hora de refactorizar. Ven el riesgo de cambiar el código y el costo de desarrollo a invertir, pero no ven el riesgo de mantener un diseño torpe, pesado, incómodo y el costo de trabajar sobre un diseño difícil de manejar.

Es preciso refactorizar cuando:
  • El diseño no expresa el entendimiento actual del equipo sobre el dominio
  • Conceptos importantes del dominio están implícitos en el diseño (y descubrimos una forma de hacerlos explícitos)
  • Encontramos una oportunidad de hacer más flexible una porción importante del diseño
Sin embargo, todo tiene un límite:
  • No debemos refactorizar el día anterior al lanzamiento de la release
  • No debemos introducir modificaciones que sólo demuestran virtuosismo técnico y no aportan valor al modelo
  • No debemos introducir mejoras cuando no podemos convencer al experto del dominio de usarlas
  • No debemos quedarnos con lo que tenemos, si sabemos que podemos tener algo mejor

Las Crisis son Oportunidades

Un período de refinamientos continuos puede traernos de pronto una visión que sacuda todo lo que tenemos hasta ahora. Los Progresos no ocurren todos los días. Los Progresos son el resultado de refactorizaciones continuas en las que modelos más profundos y diseños más flexibles fueron emergiendo. Con frecuencia, estos momentos se ven como crisis y no como oportunidades. De repente el modelo que usábamos resulta inadecuado y todo el equipo entra en pánico.


Esto no es el fin, sino el comienzo de un nuevo nivel de entendimiento. Desde esta nueva evolución, desde este más elevado punto de vista, el modelo que tenemos luce viejo, pobre. ¡Podemos hacer uno mucho mejor!

Por eso, la refactorización hacia una visión más profunda es un proceso continuo.

domingo, 15 de noviembre de 2009

DDD - Parte 3: Refactorizando Hacia una Visión más Profunda #2

Patrón 1: Intention-Revealing Interfaces

Como ya estuvimos viendo en los post anteriores, con Domain-Driven Design (DDD) se busca hacer foco en la lógica de dominio. (Seguro recordarán el título de ese primer post: El Dominio es lo Único Importante.) Cuando hablamos de lógica de dominio compleja queremos hablar de reglas, de conceptos de negocio, que se interrelacionan en un modelo de dominio que puede analizarse usando distintos niveles de abstracción. El paradigma de la orientación a objetos nos permite abstraernos de los detalles del código, permite encapsular en clases e interfaces un conjunto de instrucciones confiriéndoles un nombre, un concepto, una responsabilidad. Así, cuando tengamos que comunicarnos con el experto de dominio, con clientes programadores o hasta con nosotros mismos, podremos hablar de paquetes, entidades, objetos de valor y servicios, pudiendo revisar las interfaces, la lista de operaciones, para darnos cuenta o recordar qué rol cumple cada uno.

Pero si las interfaces de los objetos de dominio están mal diseñadas y no revelan de forma explícita lo que pueden hacer y cómo deben utilizarse, estamos en un problema, porque el programador cliente que tenga que invocarlas deberá internarse en la implementación y seguir el código para entender su uso. De esta forma estamos perdiendo el tremendo valor de la encapsulación. Si un programador debe consultar la implementación de un componente para poder usarlo, el valor de la encapsulación se ha perdido.

El patrón Interfaces que Revelan sus Intenciones habla sobre darle nombres significativos a las clases y a las operaciones que describan sus efectos y propósitos. Una buena elección de nombres, de distribución de métodos, de parámetros, alivia al programador cliente de la necesidad de entender los mecanismos internos del componente. Estos nombres deberían ajustarse a los del lenguaje ubicuo para que los miembros del equipo puedan inferir rápidamente su significado. Todos los mecanismos complicados deben estar encapsulados dentro de una interfaz abstracta que se comunique en términos de intenciones, en lugar de algoritmos.

Para ayudar a la buena elección de nombres, a la feliz distribución de métodos, parámetros, paquetes, también se pueden considerar los test unitarios. Los test pueden comunicar mucho más que un documento verboso o que un diagrama. Los test revelan de forma explícita el comportamiento de las interfaces, desnudando las entradas y las salidas, comunicando directamente al programador cliente la forma de uso del componente.

Patrón 2: Side-Effect-Free Functions

A groso modo, las operaciones pueden dividirse en dos categorías:
  • Comandos (commands)
  • Consultas (queries)

Las consultas obtienen información del sistema sin modificarlo (por ejemplo un getter). Los comandos son operaciones que introducen un cambio en el sistema (por ejemplo un setter). En una aplicación compleja, muchas operaciones llaman a otras operaciones y éstas pueden invocar a muchas operaciones más. De esta forma, se vuelve muy difícil anticipar todas las consecuencias de invocar una operación.

En teoría de programación las operaciones que devuelven un resultado sin producir un efecto secundario (side effect), un cambio en el sistema, son llamadas funciones. Una función puede ser llamada múltiples veces y retornar el mismo valor cada vez. Las funciones son mucho más fáciles de probar que las operaciones que tienen efectos secundarios. Las funciones implican menor riesgo.

Evans aconseja usar Funciones Libres de Efectos Secundarios siempre que sea posible y sobre todo mantener los comandos y las consultas estrictamente separados en diferentes operaciones. Además, siempre que se pueda devolver un Objeto de Valor (ValueObject) como resultado de un cálculo es preferible antes que una Entidad. Un Objeto de Valor puede crearse en el momento, para devolver el resultado. En cambio, como vimos en post anteriores, las Entidades tienen un ciclo de vida bastante más complicado. Los Objetos de Valor son inmutables, lo que implica que, desde el punto de vista del modelo, todas sus operaciones son funciones.

Patrón 3: Assertions

Separar estrictamente las funciones y los comandos es un excelente comienzo. Y si además hacemos que estas operaciones formen parte de una interfaz declarativa, que revela explícitamente sus intenciones, mucho más. Pero lamentablemente no nos podemos librar de las operaciones que producen cambios en el sistema, sino nuestros diseños no tendrían mucha utilidad. El patrón Aserciones (Assertions) hace explícitos los efectos secundarios de los comandos, los declara para facilitar su uso. Las Aserciones describen estados, no procedimientos, por lo tanto son fáciles de analizar.

Con Aserciones podemos establecer las post-condiciones de una operación y las invariantes de clases y agregaciones. Si las Aserciones no pueden codificarse directamente en el lenguaje de programación, siempre podemos escribir pruebas unitarias. Los test unitarios siempre son buenos porque al configurarlos podemos hacer explícitas las pre-condiciones de la operación y, después de la ejecución, chequeamos con Aserciones que las post-condiciones se hayan cumplido.

En Java, por ejemplo, una prueba unitaria muy simple, usando Aserciones podría ser:
public void testSuma() {
int sumando1 = 25;
int sumando2 = 5;
int resultado = suma(sumando1, sumando2);
assertEquals(30, resultado);
}
Evans dice que las Aserciones no sólo deberían usarse en los unit test, también deberían utilizarse en el código mismo, y poderse activar o desactivar por configuración, dependiendo del ambiente en que estemos trabajando.

Patrón 4: Conceptual Contours

El cuarto patrón, Contornos Conceptuales (Conceptual Contours) habla de descomponer los elementos de diseño (operaciones, interfaces, clases, agregaciones, etc) en unidades cohesivas, teniendo en consideración las divisiones de importancia del dominio, observando los ejes de cambio y estabilidad a través de sucesivas refactorizaciones. Es importante modulizar el modelo, alinearlo con los aspectos consistentes del dominio y establecer contornos conceptuales que permitan desglosar un gran sistema en áreas de conocimiento distintas.

El objetivo es un simple conjunto de interfaces que se conbinen de forma lógica, descubriendo así de forma explícita el lenguaje ubicuo en el modelo y en el código, y apartando los aspectos irrelevantes. No hay una receta para esto. No hay procesos fijos establecidos que ayuden a lograr acertados contornos conceptuales. Lamentablemente, sí hay que tener mucha intuición, y mucho conocimiento del dominio. Comúnmente los contornos conceptuales son resultados de muchas refactorizaciones orientadas al negocio, muchas refactorizaciones rumbo hacia una visión más profunda.

Patrón 5: Standalone Classes

Otra verdad cruel: las interdependencias complican el entendimiento de los modelos y de los diseños, hacen que sean más difícil de testear y de mantener y, por lo general, suelen acumularse fácilmente.

El objetivo de los patrones de Módulos y Agregaciones (ver posts: Los Componentes Fundamentales de un Modelo Guiado por el Dominio Parte 1 y Parte 2) es limitar la red de interdependencias entre los objetos. Pero incluso dentro de un Módulo o dentro de una Agregación, la red de interdependencias puede ser lo suficientemente compleja para retrasar severamente la evolución del proyecto. Por eso, cuando se está diseñando, se debe pensar con cuidado cada dependencia. Cada dependencia debe ser sospechosa hasta demostrar que es indispensable para definir al modelo. El bajo acoplamiento entre clases y paquetes es fundamental. El objetivo no es eliminar todas las dependencias, eso es imposible, pero sí eliminar todas las dependencias que no son esenciales.

Refactorizar las más intrincadas dependencias en Clases Independientes (Standalone Classes) es el objeto supremo de este patrón. El más bajo acoplamiento es la forma más básica de reducir la sobrecarga conceptual en un modelo, y una Clase Independiente, una clase suelta, es el acoplamiento más bajo que se puede lograr. (Por supuesto que el nombre del patrón es más bien una metáfora; no se puede construir un sistema con todas clases individuales sin relaciones entre sí, pero sí se puede aspirar a llegar al más bajo acoplamiento posible.)

Patrón 6: Closure of Operations

Los matemáticos son fanáticos acerca de no mezclar conceptos distintos. Cuando uno suma un número entero con otro número entero, el resultado también es un número entero. Entonces se dice que los números enteros son cerrados bajo la operación de la suma. La propiedad de clausura provee un modo de definir una operación sin la necesidad de involucrar ningún otro concepto foráneo. El uso básico de XSLT es transformar un documento XML en otro documento XML. Esta operación es cerrada bajo el conjunto de los documentos XML. La propiedad de clausura simplifica tremendamente la interpretación de una operación y es fácil encadenar o combinar operaciones cerradas.

En diseño y/o programación esto se traduce en: siempre que se pueda, hacer que el objeto que devuelva una operación sea del mismo tipo que sus parámetros. Una operación cerrada provee una interfaz de alto nivel sin introducir una dependencia con otros conceptos.

Este patrón, Clausura de Operaciones (Closure of Operations) se puede aplicar con mayor facilidad en operaciones con ValueObjects. Lo que no quita que pueda aplicarse también a Entidades.


Igual que con los patrones de la Parte 2, el libro da muchos ejemplos, y hasta dedica una sección entera a desarrollar un caso didáctico en el que Evans tiene oportunidad de aplicar todos los patrones aquí nombrados. Yo no suelo poner ejemplos porque me interesa más extractar los conceptos principales (además de que me pasaría la vida entera resumiendo cada capítulo). Para los ejemplos detallados está el libro.

Lo que sigue, creo yo, es la parte más interesante de Domain-Driven Design: Diseños Declarativos y Lenguajes Específicos de Dominio (DSL=Domain Specific Languages).

Diseños Declarativos

No importa qué tan guiado por el modelo sea nuestro diseño, siempre terminaremos escribiendo métodos o procedimientos para producir el efecto de las interacciones conceptuales, y siempre gastaremos tiempo en escribir código repetitivo que no agregue significado o comportamiento.

El Diseño Declarativo es una forma de escribir un sistema, o parte de un sistema, como si se tratara de una especificación ejecutable, una muy precisa descripción de propiedades que controlan el software. A nivel de código, esto puede ser construido de dos maneras:
  • A través de un mecanismo de reflexión, en tiempo de ejecución
  • A través de generación automática de código, en tiempo de compilación, produciendo código convencional basado en la declaración
Este enfoque permite a otro desarrollador tomar la declaración como un valor literal, como una garantía absoluta.

Generar un programa ejecutable a partir de una declaración de propiedades del modelo es una especie de Santo Grial para un diseño guiado por el modelo (Model-Driven Design), pero Evans asegura que en la práctica pueden presentarse problemas. Dos de los problemas que plantea son bastantes comunes e incluso a mí me ha tocado la desgracia de experimentarlos:
  • Un lenguaje declarativo nunca va a expresar lo suficiente como para hacer TODAS las cosas que son necesarias. Entonces llega la hora de tocar "a mano" el código generado. Entonces llegan los dolores de cabeza, porque a menudo las porciones de código auto-generadas son muy difíciles de extender
  • El ciclo iterativo de desarrollo se hace casi imposible de seguir, ya que muchas veces agregar código "a mano" en código que fue auto-generado, provoca que la herramienta no pueda volver a regenerar el código, que al regerar el código se pierda lo escrito "a mano", o que el programa se destruya por completo. En otras palabras: las técnicas de generación de código hacen que perdamos control.
La programación orientada a reglas con un motor de inferencia, como puede ser JBoss Drools (ya mencionado otras veces en este blog y del que pueden encontrar bastante material en el blog de Salaboy) es otro enfoque prometedor de Diseños Declarativos. Sin embargo, a pesar de que la programación orientada a reglas es declarativa en principio, muchos sistemas tienen lo que se llaman predicados de control que permiten agregar configuraciones de rendimiento. Este código de control introduce efectos secundarios, por lo tanto el comportamiento ya no está completamente dictado por las reglas declarativas, sino que hay que tener en cuenta "otros detalles" a la hora de introducir modificaciones.

Aunque Evans no lo menciona, también creo que la programación orientada o procesos haciendo uso un motor de workflows, es otro ejemplo de Diseño Declarativo muy interesante (ejemplo: JBoss jBPM; también pueden encontrar material en el blog de Salaboy).

Tenemos mucho más ejemplos de programación declarativa embebida en la programación imperativa de hoy. Basta con citar muchas herramientas comunes como los ORMs (Hibernate, TopLink, etc), los frameworks MVC para las capas de presentación, las herramientas de inyección de dependencia provistas por Spring, EJB3 o tantas otras especificaciones y/o implementaciones. Este tipo de frameworks trabajan con mecanismos de reflexión. Muchas de ellas son muy estables, cumplen funcionalidades específicas y alivian al programador de mucho código repetitivo y propenso a errores.

DSL: Domain-Specific Language

Los lenguajes específicos de dominio son la expresión máxima de Domain-Driven Design. Los DSL son lenguajes creados para resolver un problema particular de dominio. Pueden ser declarativos o no, no es necesario que lo sean. Ejemplos existen muchos: SQL es un DSL, un lenguaje que sirve para la comunicación con un motor de base de datos, SMI es otro DSL, un subconjunto adaptado de ASN.1, que sirve para definir árboles MIB en el protocolo SNMP de administración de redes, otro ejemplo de DSL puede ser GPSS, un viejo lenguaje declarativo de simulación creado por IBM, donde no se programa de forma imperativa mediante instrucciones, sino mediante bloques declarativos que especifican lo que las transacciones (objetos transitorios) tienen que hacer en un modelo simulado.

Nosotros también podemos crear nuestro propio DSL, si la complejidad del dominio lo amerita; podemos crear un DSL para construir aplicaciones bancarias, un DSL para construir aplicaciones para empresas de transporte de larga distancia, y todo lo que nos podamos imaginar. Estos lenguajes podrían ser interpretados con un intérprete programado en algún lenguaje imperativo (usando un parser y mecanismos de reflexión) o podrían ser "compilados" escupiendo código auto-generado (el código que se genere podría ser imperativo: Java, C++, lo que sea).

La ventaja de diseñar un DSL es que podemos programar de forma extremadamente expresiva, haciendo que los términos del lenguaje ubicuo pasen a formar parte de los tipos de datos y de las expresiones que se pueden escribir en el programa de forma nativa. Sin embargo, a pesar de que suena muy excitante, hay muchas desventajas también:
  • El costo de inversión es muy grande al principio. Hasta que el DSL y el lenguaje ubicuo estén estables y pueda realmente construirse aplicaciones usándolos puede pasar mucho tiempo
  • La habilidad (skill) de los desarrolladores tiene que ser muy grande. Deben tener mucha experiencia en varios lenguajes de programación. Mucha experiencia en el lenguaje sobre el que estén construyendo el DSL. Mucha experiencia en teoría de lenguajes en general
  • El desarrollo en general será más complejo. Las refactorizaciones serán más complicadas. El código que soporta el DSL no será fácil de construir, ni fácil de interpretar. Los cambios en el equipo de desarrollo podrían ser fatales
Para terminar con este post, Evans dice que no siempre la programación orientada a objetos es la mejor opción para construir un DSL. Un lenguaje de la familia de los lenguajes funcionales tal vez podría alinearse mejor al paradigma de los DSL.

jueves, 5 de noviembre de 2009

DDD - Parte 3: Refactorizando Hacia una Visión más Profunda #1


Domain-Driven Design (DDD) dice que el verdadero desafío en el desarrollo de una aplicación empresarial es encontrar un modelo incisivo, que capture hasta los conceptos más sutiles del dominio y los vuelque en un diseño práctico. Refactorizar es una práctica que conduce a ese modelo. Refactorizar es rediseñar de forma tal que no se altere la funcionalidad del sistema. Para esto puede ayudar un buen conjunto de pruebas automatizadas que nos permitan experimentar con el código y el modelo con relativa tranquilidad.

Evans habla de tres niveles de refactorización:
  • Nivel 1: cambios mecánicos que hacen más fácil de leer el código
  • Nivel 2: cambios que permiten aplicar patrones de diseño
  • Nivel 3: cambios que permiten obtener un modelo más profundo
Un modelo profundo (deep model) proporciona una lúcida expresión de las incumbencias principales de un dominio y de su conocimiento más importante, a la vez que aparta los aspectos superficiales.

Progreso

El retorno de inversión de la refactorización no es lineal. Las revelaciones más importantes surgen abruptamente y así un modelo cada vez más profundo va emergiendo gradualmente tras jornadas discontinuas de refactorización. Cada refinamiento del código y del modelo aclara el panorama del equipo de trabajo. Este tipo de progreso (breakthrough) no es una técnica, es un evento.


A menudo, cuando la perspectiva de un progreso se presenta resulta aterrador. El calendario siempre es apretado y decisivo. Pero hay que recordar que estos progresos pueden implicar importantes adelantos en el desarrollo del proyecto. Evans nos recomienda que no nos paralicemos tratando de lograr un progreso. Usualmente los progresos se presentan solos después de varias modestas refactorizaciones. Concentrémonos en masticar el conocimiento y en cultivar un lenguaje ubicuo robusto, como se habló en el post dedicado a la primera parte del libro.

Haciendo Explicitos los Conceptos Implícitos

Muchas transformaciones del modelo de dominio ocurren cuando los desarrolladores reconocen un concepto que acaba de insinuarse en una en una discusión o que estaba implícito en el diseño y encuentran la forma de representarlo de forma explícita con uno o más objetos o relaciones. Por eso, los desarrolladores deben estar atentos a este tipo de insinuaciones y algunas veces deben salir a buscarlas de forma proactiva. Muchos de estos descubrimientos surgen de:
  • Prestar atención al lenguaje del equipo
  • Eliminar torpezas en el diseño y aparentes contradicciones en las declaraciones de los expertos
  • Explorar la literatura del dominio
  • Hacer mucha y mucha experimentación
Cuando el usuario o el experto de dominio usan palabras que no están en el lenguaje ubicuo o en el diseño, estamos frente a un signo de advertencia bien grande. Es una advertencia doblemente fuerte cuando no sólo los usuarios sino también los desarrolladores usan este vocabulario ajeno al modelo. Es cuando tenemos que sentarnos a refactorizar.


Los conceptos que necesitamos no siempre están flotando en la superficie, ni emergiendo en las conversaciones o los documentos. Tendremos que cavar, incluso excavar, incluso inventar. El lugar donde cavar es en la parte más débil de nuestro diseño, el lugar donde los procedimientos son cosas complicadas, retorcidas y difíciles de explicar, el lugar donde cada nuevo requerimiento parece agregar más complejidad.

A veces, para descubrir estos conceptos ocultos contamos con un experto de dominio colaborador, a veces no. A veces contamos con bibliografía donde los conceptos están bien definidos y detallados, a veces no. Muchos dominios no tienen modelos tan refinados como los de contabilidad o finanzas, pero en otros casos podemos dar con pensadores en el campo, quienes quizás se tomaron el trabajo de organizar y abstraer las prácticas comunes del negocio, o artículos especializados, white papers, blogs, etcétera.

En DDD la experimentación es una de las prácticas más importantes. La experimentación es la forma de saber si algo funciona o no. En el camino a un modelo profundo hay mucha prueba y error, mucha obstinación y muchas lecciones aprendidas.

¿Cómo modelar los conceptos menos obvios?
El paradigma de la orientación a objetos nos lleva a buscar y a inventar cierto tipo de conceptos. Son los famosos sustantivos y verbos que nos enseñan cuando somos neófitos en la materia, intentando en vano simplificar el proceso de relevamiento de un dominio. Pero hay otras categorías de conceptos también muy importantes:


1) Restricciones Explícitas

Algunas veces las restricciones (constraints) encuentran un hogar dentro de un objeto o un método cuya responsabilidad no está relacionada con la restricción de forma directa, pero cuando la lógica se torna más complicada, es conveniente aislar la regla en una porción de codigo aparte (sea método o clase), donde la restricción tenga espacio para crecer naturalmente. Si la regla es más complicada debe tener su clase propia y hasta su paquete.


Hay tres advertencias que podemos recibir de que una regla o restricción está contaminando la clase en donde se ubicó y debe mudarse a un lugar más espacioso:
  • Evaluar la restricción requiere datos que no tienen que ver con la definición del objeto
  • Reglas relacionadas aparecen en múltiples objetos, forzando duplicamiento o herencia de objetos que no deberían pertenecer a la misma familia
  • Un montón de diseño, requerimientos y conversaciones giran alrededor de la restricción, pero en la implementación la regla está oculta en un código procedural
DDD propone un patrón para encapsular reglas de negocio un poco más complicadas. Es el patrón Specification que ya mencioné en algún post pasado. En el libro hay una extensa y detallada explicación que no tiene desperdicio. También en el libro de Fowler, Patterns of Enterprise Application Architecture, que ya estuve comentando en los post de Patrones Enterprise Parte 1 y Parte 2, hay una explicación exhaustiva. La versión de Evans es un poco más sofisticada. Para definir los Specifications se inspira en los predicados del paradigma de programación lógica. Además, si se quedaron con ganas de seguir leyendo sobre el tema, hay un paper que Eric Evans y Martin Fowler escribieron juntos y que se puede descargar desde este link. (Algún día, si tengo tiempo, me gustaría escribir un post exclusivo sobre Specifications, pero por ahora voy a saltear el tema.)

2) Procesos como Objetos de Dominio
Reglas de negocio y procesos son dos grandes categorías de conceptos de modelo que no vienen a la mente cuando estamos programando en un lenguaje orientado a objetos. Los procesos pueden expresarse con el patrón Service (ver DDD - Parte 2: Los Componentes Fundamentales de un Modelo Guiado por el Dominio #1, donde hay un breve resumen sobre los Services). La clave para distinguir un proceso que debe ser expuesto de forma explícita en el modelo de otro que debe permanecer escondido entre Entidades y ValueObjects, es simple: ¿El proceso es algo de lo que el experto de dominio habla mucho, o sólo se trata de un aspecto técnico, de un proceso algorítmico del código?

Si bien los patrones Specification (para expresar reglas de negocio) y Service (para expresar procesos) son elegantes y poderosos, hay casos en los que no resultan suficientes y tenemos que adentrarnos en el mundo de los motores de reglas de negocios y de workflows.

Diseño Flexible

El propósito final del software es servir a los usuarios. Pero primero, ese mismo software debe servir a otros desarrolladores. Cuando un sistema con un comportamiento complejo carece de un buen diseño, se vuelve difícil de refactorizar y de manipular. El diseño flexible (supple design) es el complemento de un modelo profundo.

Simple no significa fácil. Para crear elementos que puedan ensamblarse en elaborados sistemas y mantenerse simples se necesita de un estilo de diseño moderadamente riguroso. Para abrazar el cambio, el diseño tiene que ser fácil de entender y, por ende, fácil de modificar. Las primeras versiones suelen ser rígidas, toscas; sólo a través de sucesivas refactorizaciones y sucesivos progresos, podremos alcanzar un diseño flexible.


No hay ciencia ni fórmula para lograr esto; sin embargo, Evans propone un conjunto de patrones que contribuyen a alcanzar el (o por lo menos a mantenerse en el camino hacia el) diseño flexible:
  • Intention-Revealing Interfaces
  • Side-Effect-Free Functions
  • Assertions
  • Conceptual Contours
  • Standalone Classes
  • Closure of Operations


En el próximo post nos dedicaremos a ver un resumen de cada uno de estos patrones y hablaremos un poco sobre Diseños Declarativos y lo que es la máxima expresión de DDD: Domain-Specific Language.

domingo, 14 de junio de 2009

Scrum

Scrum es un proceso de desarrollo de software iterativo e incremental utilizado comúnmente en entornos basados en el desarrollo ágil de software. Fue aplicado por Jeff Sutherland y formalizado por Ken Schwaber. Poco después, Sutherland y Schwaber se unieron para refinar y extender Scrum.

Scrum no está concebido como método independiente, sino que se promueve como complemento de otras metodologías, incluyendo XP, MSF o RUP. Enfatiza valores y prácticas de gestión, sin pronunciarse sobre requerimientos, implementación y demás técnicas. Se define como un proceso de management y control que implementa técnicas de control de procesos.

Scrum se basa en los siguientes principios ágiles:
  • Colaboración estrecha con el cliente
  • Predisposición y respuesta al cambio
  • Prefiere el conocimiento tácito de las personas al explícito de los procesos
  • Desarrollo incremental con entregas funcionales frecuentes
  • Comunicación verbal directa entre los implicados en el proyecto
  • Motivación y responsabilidad de los equipos por la auto-gestión, auto-organización y compromiso
  • Simplicidad. Supresión de artefactos innecesarios en la gestión del proyecto


Roles

Product Owner
  • Representa a todos los interesados en el producto final
  • Financia el proyecto
  • Provee los requerimientos del sistema
  • Prioriza los features
  • Adapta los incrementos de cada Sprint
Scrum Master
  • Representa el management del proyecto
  • Es responsable de velar y difundir los valores y las prácticas de Scrum
  • Su tarea principal es remover impedimentos
Equipo de Desarrollo
  • Típicamente de 5-10 personas
  • Multi-funcional
  • Los miembros deben ser full-time
  • Los equipos son auto-organizativos
  • Sólo puede haber cambio de miembros entre los sprints

Ciclo de Vida de Desarrollo



El Sprint es el período de tiempo durante el que se desarrolla un incremento de funcionalidad. Constituye el núcleo de Scrum, que divide de esta forma el desarrollo de un proyecto en un conjunto de pequeñas "carreras". La duración máxima de un sprint es de 30 días. Durante el sprint no se puede modificar el trabajo que se ha acordado en el Sprint Backlog. Sólo es posible cambiar el curso de un sprint, abortándolo, y sólo lo puede hacer el Scrum Master si decide que no es viable por algunas de las razones siguientes:
  • La tecnología acordada no funciona
  • Las circunstancias del negocio han cambiado
  • El equipo ha tenido interferencias


Artefactos

Product Backlog
Listado con los requerimientos del sistema.
Contiene: features, requerimientos de desarrollo (no funcionales), tareas investigativas, bugs.

Es responsabilidad del Product Owner:
  • Contenido,
  • Priorización,
  • Disponibilidad
Está dado por una combinación de:
  • Trabajo basado en funcionalidad
  • Trabajo basado en tareas
Es un documento dinámico que incorpora constantemente las necesidades del sistema. Se mantiene durante todo el ciclo de vida (hasta el retiro del sistema).

Sprint Backlog
  • Trabajo o tareas determinadas por el equipo para realizar en un sprint y lograr al final del mismo un incremento de funcionalidad
  • Las de mayor duración deben intentar descomponerse en sub-tareas de ese rango de tiempo
  • En Scrum se debe ir registrando los tiempos día a día para poder armar el gráfico de avance del proyecto
  • El equipo agrega tareas cuando lo crea necesario, pudiendo eliminar las que considere innecesarias, y ajusta estimaciones a medida se avanza
Burndown Chart

El Burndown Chart es una gráfica mostrada públicamente que mide la cantidad de requisitos en el Backlog del proyecto pendientes al comienzo de cada Sprint. Dibujando una línea que conecte los puntos de todos los Sprints completados, podremos ver el progreso del proyecto. Lo normal es que esta línea sea descendente (en casos en que todo va bien en el sentido de que los requisitos están bien definidos desde el principio y no varían nunca) hasta llegar al eje horizontal, momento en el cual el proyecto se ha terminado (no hay más requisitos pendientes de ser completados en el Backlog). Si durante el proceso se añaden nuevos requisitos la recta tendrá pendiente ascendente en determinados segmentos, y si se modifican algunos requisitos la pendiente variará o incluso valdrá cero en algunos tramos.


Reuniones

Spring Planning Meeting: Acude el equipo, el Scrum Master y el Product Owner.
  • El equipo de desarrollo toma el objetivo del Sprint y decide las tareas necesarias para los ítems del Product Backlog
  • El equipo se auto-organiza acerca de cómo alcanzar el objetivo del Sprint
  • El Scrum Master NO asigna las tareas a los individuos
  • El Scrum Master NO toma decisiones por el equipo
  • Se crea el Sprint Backlog de acuerdo a las prioridades de negocio que establece el Product Owner

Daily Meeting: Reunión diaria del equipo con duración máxima de 15 minutos.
  • Todos los días en el mismo sitio y a la misma hora
  • Se recomienda que sea la primera actividad del día
  • Deben acudir todos los miembros del equipo
  • Moderada por el Scrum Master, que pregunta a todos los asistentes:
  • ¿Cuál ha sido el trabajo realizado desde la última revisión diaria?
  • ¿Cuál es el trabajo previsto para hoy?
  • ¿Hay algo que necesitas, o que te impide realizar el trabajo previsto?
  • No se permite entrar en divagaciones o salirse del guión
  • Sólo habla la persona que informa de su trabajo, el resto escucha y no hay lugar para otras conversaciones
  • Cuando un miembro informa de algo de interés para otros, o necesita ayuda de otros, estos se reúnen al terminar la revisión diaria
  • Las personas externas que estén presentes no pueden intervenir ni distraer, y el Scrum Master puede limitar el número de personas externas asistentes si lo considera oportuno
Sprint Review Meeting: Reunión en que participa: Scrum Master, Product Owner, Equipo, todas las personas implicadas en el proyecto.
  • Duración máxima: 4 horas
  • Finalidad: presentar al Product Owner y a las demás personas las nuevas funcionalidades implementadas
  • Las funcionalidades no implementadas no se presentan
  • En la reunión, los miembros del equipo muestran las nuevas funcionalidades
  • Al final de la reunión se interroga individualmente a todos los asistentes para recabar impresiones, sugerencias de cambio y mejora, y su relevancia
  • El Product Owner trata con los asistentes y con el equipo las posibles modificaciones en el Product Backlog
Sprint Retrospective Meeting: Acude el equipo y el Scrum Master, y opcionalmente el Product Owner.
  • Todos los miembros del equipo responden a dos preguntas:

  • ¿Qué cosas fueron bien en el último sprint?
  • ¿Qué cosas se podrían mejorar?
  • El Scrum Master anota todas las respuestas
  • El Equipo prioriza las mejoras posibles
  • El Scrum Master no proporciona respuestas, sino que ayuda al equipo a encontrar la mejor forma de trabajar con Scrum
  • Las acciones de mejora localizadas que se puedan implementar en el próximo Sprint deben introducirse en el backlog como elementos no funcionales

Conclusiones

Scrum es una metodología ágil ideal para modelar proyectos críticos y complejos, en los que la calidad y velocidad de implementación son fundamentales, pero donde los requerimientos son vagos o muy cambiantes.

El énfasis de Scrum está en buscar que los proyectos de desarrollo le aporten a las organizaciones el mayor valor posible a muy corto plazo, con resultados de calidad que respondan a las necesidades reales del negocio.

A favor, tiene que:
  • Hace foco en entrega rápida de producto
  • Existe feedback continuo y frecuente sobre el progreso del desarrollo, gracias a las reuniones diarias y de revisión de sprint
  • Da libertad al equipo de trabajo de usar las prácticas de desarrollo que prefieran
En contra, tiene que:
  • Puede caer en un modelo de codificar y probar, si no se realiza un coaching
  • El QA está embebido informalmente en muchas partes del ciclo de vida
  • Establece en el equipo de desarrollo una fuerte aversión a la documentación

Fuentes

Apuntes de la materia 75.51 de FIUBA: Técnicas de Producción de Software I. Clase Scrum. Versión 1.2: Mayo 2008

domingo, 19 de abril de 2009

DDD - Parte 2: Los Componentes Fundamentales de un Modelo Guiado por el Dominio #2

En el post anterior estuvimos viendo los primeros Patrones de Dominio Fundamentales de DDD:
  • Layered Architecture
  • Associations
  • Entities
  • Value Objects
  • Services
  • Modules
En este post nos centraremos en los tres Patrones que nos faltaron:
  • Aggregates
  • Factories
  • Repositories
Los tres patrones que trataré en este post, se encargan de encapsular distintas partes del ciclo de vida de un objeto.

El Ciclo de Vida de un Objeto de Dominio

Cada objeto en el dominio tiene un ciclo de vida que puede ser más o menos complejo dependiendo de su responsabilidad y/o propósito. Hay muchos objetos que son simples, transitorios, que se crean con un constructor simple, se usan para algún cálculo y se abandonan a las piadosas manos del recolector de basura. Pero los objetos que más nos interesan en este post son aquellos cuya vida se prolonga más allá de la existencia del programa que las usa.


Estos objetos son Entidades o Value Objects, que son creados a través de un Factory, modificados en memoria y guardados, reconstruidos o archivados en un Repository.

IX. Patrón 6: Aggregates


Ejemplo típico: una Persona (un Entity) que tiene una Dirección (un Value Object). Ambos objetos están asociados mediante una composición. La Dirección pertenece a la Persona y no tiene razón de ser fuera de la misma. No se actualiza una Dirección, se actualiza una Persona, se actualiza la dirección de una Persona.

Una Agregación es un grupo de objetos asociados que deben tratarse como una unidad a la hora de manipular sus datos. En el caso del ejemplo, Persona y Dirección forman parte de una misma Agregación. Otro ejemplo típico es el de Auto, con Rueda y Motor; salvo que se trate de un dominio muy específico, los componentes del auto sólo se modificarán a través del Auto. Auto, Rueda y Motor formarán una única Agregación.


Cada Agregación cuenta con una Entidad Raíz (root) y una Frontera (boundary). La Frontera define qué está dentro de la Agregación y qué no. La Entidad Raíz es una Entidad contenida en la Agregación y será el único punto de entrada a la Agregación. Esto significa que:
  • La Entidad Raíz es responsable de chequear las reglas de consistencia (invariantes) entre los distintos objetos de la Agregación en la carga, modificación y eliminación de datos.
  • La Entidad Raíz tiene una identidad global. Las Entidades Internas tienen una identidad local. Las Entidades Internas sólo pueden ser distinguibles dentro de la Agregación.
  • Fuera del contexto de la Agregación, no puede existir ninguna referencia a un miembro que no sea la Entidad Raíz. Los demás miembros de la Asociación no existen más allá de la Frontera.
  • La Entidad Raíz puede manejar referencias a Entidades Internas de otras Agregaciones, pero estas referencias deben ser transitorias. La Entidad Raíz no puede conservar la referencia.
  • La Entidad Raíz puede manejar referencias a Value Objects de otras Agregaciones y no importa lo que suceda con ella, ya que, como vimos en el post anterior, los Value Objects son sólo valores y al no tener identidad tampoco tienen una relación fuerte con la Agregación.
  • Sólo la Entidad Raíz puede ser obtenida directamente a través de un query a la base de datos. Los demás objetos de la Agregación son obtenidos navegando a través de las Asociaciones.
  • Los objetos miembro de la Agregación pueden contener referencias a otras Entidades RaízAgregaciones.
  • Una operación de eliminación debe remover absolutamente toda la Agregación en una sola transacción. Con un lenguaje que provea un garbage collector, esto es fácil, ya que no existe ninguna referencia fuera de la Agregación salvo la de la Entidad Raíz. Por eso, al eliminar la Entidad Raíz, se eliminará la Agregación completa.
  • Cuando se hace commit sobre un cambio efectuado en cualquier objeto dentro de la Agregación, todas las reglas internas de consistencia (invariantes) de la Agregación deben ser satisfechas.

Las Agregaciones marcan el alcance de las reglas de consistencia que deben mantenerse en todas las etapas del ciclo de vida de los objetos. Los siguientes dos patrones, Factories y Repositories, operan sobre las Agregaciones, encapsulando la complejidad de las transiciones durante el ciclo de vida.

X. Patrón 7: Factories

El concepto de Factory es bien simple y quien conozca los patrones fundamentales de Erich Gama entenderá perfectamente. Los Factories sirven para encapsular la creación de objetos complejos. La creación de una Entidad, por ejemplo, podría ser tan compleja que involucre a muchas otras Entidades y Value Objects. Imaginemos que se trata de la Entidad Raíz de una Agregación. Es evidente que su creación implica la creación de muchos otros objetos y el cumplimiento de reglas de negocio y consistencia que deben hacerse cumplir. Para abstraer al cliente y al objeto en sí de este complejo mecanismo es que se provee un Factory que se encargue del trabajo sucio.


Eric Evans presenta este patrón clasificándolo como componente fundamental de un Model-Driven Design. El patrón nos dice que traslademos la responsabilidad de crear instancias de objetos complejos y Agregaciones a un objeto externo, que puede no tener responsabilidad en el modelo, pero que sigue siendo parte de la capa de dominio. Luego, proporcionemos una interfaz que encapsule todo el complejo montaje de relaciones, así el cliente puede referenciar a dicha interfaz y no a una instancia concreta.

Mediante los Factories podemos crear Agregaciones enteras al momento de instanciar la Entidad Raíz, haciendo cumplir todas sus invariantes.


Las implementaciones de Factory más populares son las de Erich Gama: Factory Method, Abstract Factory, Builder y Prototype. Los cuatro son Patrones Creacionales, según la clasificación de Design Patterns de GoF.

No es la idea explayarme demasiado en este post sobre los Factories (para eso está el libro de Evans). Evans da consejos de implementación, sugiere lugares donde deberían descansar los Factories, habla de la diferencia entre tener un mero constructor y un Factory (y cuando puede servirnos uno u otro), explica dónde podría ubicarse la lógica de consistencia, detalla las diferencias entre un Entity Factory y un Value Object Factory, y por último, concluye con el uso de Factories para la reconstitución de objetos de dominio.


Hasta la última sección siempre se sugiere que los Factories sirven para la creación de objetos (la primera transición en el ciclo de vida). Aquí se hace un fuerte énfasis en la utilización de Factories para la reconstitución de objetos desde una base de datos, una red o lo que sea. Un Factory usado para este propósito cuenta con dos principales diferencias respecto a los demás Factories de creación:
  • No asigna nuevas identidades a los Entities. Sólo reconstituye el objeto desde la persistencia, respetando su identidad ya definida.
  • Maneja distintas invariantes. Si el objeto ya existiera en algún lado del sistema, las reglas de integración exclusivas para la alta no tienen sentido en esta fase del ciclo de vida. Sin embargo, la reconstrucción puede ocasionar inconsistencias y ciertas invariantes pueden no cumplirse. El Factory debería contar con una estrategia de reparación, lo que puede complicar muchísimo más el código que en un simple Factory de creación.
Por ende, los Factories encapsulan las transiciones de creación y reconstitución del ciclo de vida del objeto. Otras transiciones cuya complejidad técnica pueden contaminar de forma no deseada la capa de dominio son las que van y vuelven desde el almacenamiento. Los encargados de encapsular estas transiciones son los Repositories.


XI. Patrón 8: Repositories


Para operar sobre un objeto, necesitamos obtener una referencia al mismo. Existen dos formas de obtener esta referencia:
  • A través de una operación de creación: un Factory, un constructor, un operador de creación nativo del lenguaje.
  • A través de una asociación entre objetos: navegando a través de la relación con otro objeto.
La segunda forma es muy poderosa y es uno de los conceptos fundamentales de la orientación a objetos. Pero para poder llegar al objeto que queremos navegando a través de la red de Asociaciones, necesitamos obtener el primer objeto de todos, el que para una Agregación sería la Entidad Raíz.

Si bien desde un punto de vista técnico, la reconstitución de un objeto es un caso concreto de la creación, desde el punto de vista del dominio, un cliente necesita un medio práctico para adquirir referencias a objetos que ya existen persistidos en una base de datos o en algún otro servidor de la red. El patrón Repository es un simple framework conceptual que encapsula el proceso de reconstitución y lo diferencia del proceso de creación encapsulado por el patrón Factory. Un Factory construye nuevos objetos; un Repository busca objetos viejos, ya creados.

Un Repository representa todos los objetos de una cierta clase como un conjunto conceptual (comúnmente emulado). Su interfaz es similar a la de una colección ordinaria, en la que uno puede agregar o remover objetos. Los Repositories encapsulan el mecanismo de reconstitución de los objetos. Reciben un simple request para obtener un objeto o una lista de objetos que satisfagan un cierto criterio y ellos se encargan de reconstruir los objetos de donde sea que haya que hacerlo, de una base de datos, de un archivo de texto, de memoria, de una red, de un web service, etc, etc.

Es importante entender que los Repositories son también conceptos del dominio. Es la forma que el modelo de dominio tiene de reconstituir Entidades y Value Objects y almacenarlos para que puedan sobrevivir más allá de la ejecución del programa. La interfaz del Repository vivirá en la capa de dominio; la implementación es posible que viva en otra capa (por ejemplo en la de Persistencia). Un Repository relegará las queries recibidas en un Data Mapper, en otro Repository o en algún otro objeto específico de infraestructura que se encargue de resolver la petición. Martin Fowler trata muy bien este tema desde el punto de vista técnico en su Patterns of Enterprise Application Architecture (del cual ya estuve hablando en post anteriores), encapsulándolo en tres patrones del tipo Object-Relational Metadata Mapping: Metadata Mapping, Query Object y Repository. Se puede encontrar una buena implementación de Repository en ese libro, una implementación anterior a DDD.

Un Repository sirve para crear la ilusión de que tenemos una colección en memoria de absolutamente todos los objetos de dominio de un tipo específico. Necesitaremos un Repository para cada tipo de objeto al que accederemos de forma global. Por supuesto que no haremos un Repository para cada objeto. A las demás Entidades y Value Objects accederemos a través de las relaciones de este objeto reconstituido con el Repository. Como regla general, es casi seguro que tengamos un Repository por cada Entidad Raíz de cada Agregación.

Conclusión

Resumiendo, la Parte 2 de Domain-Driven Design: Tackling Complexity in the Heart of Software (ISBN: 0-321-12521-5) habla de los componentes fundamentales de un Model-Driven Design. Comienza con un patrón, Layered Architecture, que nos dice que una forma apropiada de aislar el dominio del resto de la aplicación es a través de una correcta separación de capas. Hay muchas formas de dividir en capas una aplicación, una de las más comunes es la que ya venimos viendo en los últimos posts: Presentación / Acceso al Dominio / Dominio / Persistencia / Base de Datos. En la capa de Dominio es donde vivirá toda la lógica de negocio y los datos sobre los cuales esta lógica opera.

(Notar que muchos frameworks técnicos que actualmente hay en el mercado organizan la aplicación de diferentes maneras. Algunas separaciones en capas pueden ir totalmente en contra de los principios de DDD. Por ejemplo, en Java EE se ha definido una especificación para aplicaciones enterprise (EJB 3), cuya arquitectura no es del todo compatible con la arquitectura de DDD. La división de capas en Java EE 5 es Presentación / Lógica de Negocio / Persistencia / Base de Datos. A primera vista uno podría pensar que la única diferencia entre una arquitectura y otra es que la de EJB 3 carece de Acceso al Negocio (o Service Layer). Esta diferencia es menor y realmente no es importante, ya que no se trata de una diferencia conceptual. La Service Layer sirve para desacoplar la Presentación del Dominio, que tranquilamente se puede implementar en Java EE. El problema fundamental es que EJB 3 separa en distintas capas la lógica de negocio y los datos con los que esta lógica opera. En la capa de Lógica de Negocio viven los Session Beans que implementan toda la lógica del dominio, todas las operaciones específicas del negocio, y en la capa de Persistencia viven los Entity Beans, que representan el modelo de objetos de negocio y contienen el mapeo objeto-relacional para que el Entity Manager pueda ejecutar queries a una base de datos relacional. Eric Evans nos recuerda constantemente que los Entity Beans de EJB no tienen nada que ver con el patrón Entity de DDD, por más que los nombres puedan confundirnos. Evans dice que al separar la lógica de negocio de las Entidades en dos capas distintas estamos perdiendo la portabilidad, la reutilización, los conceptos y el conocimiento del dominio. Estamos desperdigando el modelo, mezclándolo con incumbencias técnicas y perdiendo la aislación, requerimiento fundamental para una correcta utilización de DDD.

A quien le interese saber más sobre EJB3, en el blog de Tecnologías Java hay una introducción al tema y, en un futuro, es muy probable que me explaye un poco más sobre esta incompatibilidad de arquitecturas.)

Una vez aislado el dominio, Evans nos cuenta que existen Asociaciones entre los objetos de dominio y que es muy importante limitar la cantidad de ellas. Para ello nos da un conjunto de tips bastante útil a la hora de modelarlas. En el dominio pueden existir tres tipos de objetos: Entities, Value Objects y Services. Los Entities son objetos de dominio que tienen identidad y que por lo tanto son distinguibles. Los Value Objects no tienen identidad, por ende no son distinguibles; simplemente son objetos que representan algún valor o estado. Los Services son objetos puramente funcionales, que publican servicios para que sean consumidos por otros objetos de dominio o por la capa superior, y que manipulan Entities y Value Objects. Cuando un modelo crece bastante como para ser manejado de forma atómica, Evans nos enseña a separarlo en Módulos utilizando algún mecanismo provisto por el lenguaje de programación utilizado (en el caso de Java, los Módulos serían Paquetes).

A continuación, Evans se enfoca en el ciclo de vida de los objetos de dominio. Dice que un objeto nace, a través de un Factory o de un código simple de creación, se manipula en memoria y luego se almacena en un Repositorio de objetos de su mismo tipo. Este objeto puede ser vuelto a pedir al Repositorio en cualquier momento para volver a ser modificado y volverlo a guardar, archivarlo o borrarlo. Gracias a los Repositorios, el dominio puede abstraerse de la infraestructura que reside en la capa inferior y que se encarga de la Persistencia de los objetos.

Pero no todos los objetos serán accedidos directamente a través de Repositorios. Habrá objetos que se manipularán indirectamente, a través de Asociaciones establecidas con el objeto que sí se trajo del Repositorio. Así se establecen las Agregaciones, que encapsulan un grupo de Entidades y Value Objects que serán creados, modificados y borrados como una sola unidad. Vistas desde afuera, las Agregaciones cuentan con una Entidad Raíz, que es la que generalmente será recuperada desde un Repositorio, y una Frontera, que encerrará un conjunto de objetos internos que son invisibles al exterior. (Una aclaración que no he mencionado antes es que no siempre la raíz de una Agregación tiene que ser una Entidad; puede haber excepciones; pero el caso más común es que sea una Entidad, cuya identidad única le brinda una identidad única a la Agregación entera.)


En este capítulo se habla también de otras cosas. Se habla de los distintos paradigmas de modelado que pueden convivir en una sola aplicación. Se hace incapié en la impedancia de paradigmas que se da con más frecuencia en una aplicación enterprise: la del modelo de objetos con el modelo relacional. Evans da consejos muy útiles para el diseño de objetos que serán persistidos en una base de datos relacional (tema que no he tratado en los posts y que es altamente recomendable leer desde el libro).


Por último, dedica una sección completa a un ejemplo bien completo en el que pone en práctica cada uno de los conceptos fundamentales explicados. El ejemplo también lo dejo para que lo lean del libro.

------------------------------------------


DDD no termina aquí. A esta segunda parte le continúan dos partes más, espectaculares. Una habla sobre Refactoring del Código y del Diseño hacia una mejor comprensión del Dominio, que trata temas fascinantes como, entre otros, el modelado de diseños flexibles, formas de lograr saltos importantes en la comprensión del dominio, formas de llevar a la luz conceptos que hasta el momento estaban implícitos (y ocultos) y una interesante aplicación sobre los Patrones de Análisis de Martin Fowler. La otra parte habla sobre Diseños Estratégicos. Sobre todo se enfoca en aplicaciones enterprise, aplicaciones con dominios inmensos, donde el modelo de dominio debe ser construido por distintos equipos de trabajos. Trata los temas de mantenimiento de la integridad del modelo, destilación de un modelo, estructuras de gran escala (modelos bien bien grandes) y cierra con un capítulo donde combina todos los patrones y estrategias vistas en esta sección.

Como se habrán dado cuenta los que llegaron hasta acá, Domain-Driven Design: Tackling Complexity in the Heart of Software (ISBN: 0-321-12521-5) es un libro que no tiene desperdicio.