La arquitectura es una disciplina que estudia cómo garantizar ciertas cualidades del software; cualidades como la disponibilidad, la continuidad, la mantenibilidad, la escalabilidad, la reusabilidad, la observabilidad, etc. Dicho esto, es importante entender que no es posible diseñar un software que goce del máximo nivel en todas las cualidades. O, dicho de otro modo, ciertas cualidades son opuestas con otras.

De ahí, podemos extraer una conclusión: no hay diseño perfecto en el software, lo que nos obliga a entender en profundidad las necesidades del negocio para aplicar la arquitectura que garantice las cualidades necesarias en nuestro contexto, teniendo muy presente que otras tendrán que ser sacrificadas.

En este post extraemos los conceptos de Domain Driven Design, que nos ayudarán a diseñar soluciones adaptables, y analizamos las arquitecturas Hexagonal y Onion, que nos servirán para diseñar un software muy portable. Ambas cualidades, la adaptabilidad y la portabilidad, son perseguidas por los desarrolladores que deciden optar por los microservicios, arquitectura distribuida que, a la vez, sacrifica o complica las características como la observabilidad, la consistencia de la información, la latencia o la automatización.

Introducción a Domain Driven Design

Domain Driven Design (DDD) es una aproximación holística al diseño de software que pone en el centro el Domain; es decir, el dominio o problema de negocio. Es una disciplina que publicó en 2003 Eric Evans en el famoso libro Domain Driven Design, Tackling Complexity in the Heart of Software.

DDD nos ayuda, en primer lugar, a definir exhaustivamente el dominio. Nos permite también conocer de modo extenso el problema de negocio de manera que podamos dividirlo en subdominios con el objetivo de maximizar el desacoplamiento entre los subdominios y lograr así soluciones modulares, que premian la adaptabilidad de cada subdominio y, por lo tanto, la adaptabilidad de la solución en general.

Este diagrama es la representación de los conceptos que maneja DDD. Los conceptos de abajo son los necesarios para analizar el dominio en su totalidad. Mientras, que los que están en la parte de arriba del gráfico están relacionados con la arquitectura de software. Esto nos lleva a la conclusión de que DDD es una aproximación al diseño de soluciones de software que abarca desde la definición más holística, hasta la implementación de los objetos en el código.

Conceptos clave

Hagamos ahora una pequeña introducción de los conceptos que DDD emplea para lograr dicha definición modular del dominio.

  1. Domain

Domain es el problema de negocio que analizamos, para dar una solución basada en software. El Domain puede dividirse en Domains más pequeños. Lo que lo convierte en un concepto fractal. Además DDD nos indica los distintos tipos de Domain que existen, los clasifica en base a la relevancia que tienen para la estrategia de la empresa.

  1. Ubiquitous Language

Este es un concepto muy importante en DDD. Como ya decía Vygotsky, "el lenguaje es un regulador del pensamiento", y DDD recupera la importancia del lenguaje. Pero, en esta ocasión, para pensar, analizar y definir el Domain.

DDD define el Ubiquitous Language como la jerga que los expertos de negocio emplean de manera natural para articular y para definir su problema de negocio, su Domain.

Eric Evans nos insta a ser ortodoxos con el Ubiquitous Language durante las sesiones de definición del Domain, pero también a serlo de manera explícita en el código fuente. Nuestro software debe expresar con claridad el Ubiquitous Language.

Es un concepto de gran importancia porque, además de servir de vehículo de entendimiento en el negocio y entre el negocio e IT, también sirve para identificar las particiones del Domain, que darán lugar a soluciones modulares. Para ello Eric Evans nos indica que tenemos que estar atentos durante las sesiones de definición del Domain para advertir cuando los expertos del negocio comienzan a tener discrepancias en el entendimiento sobre un concepto del negocio.

A continuación, un ejemplo claro de discrepancia: el departamento comercial de una energética, no tiene la misma conceptualización de la entidad Punto de Suministro, que la conceptualización del departamento de mantenimiento.

Estas discrepancias son el hecho que nos permite identificar distintos subdomains. En el ejemplo anterior tenemos dos Domain o Subdomain, el Domain comercial y el Domain mantenimiento.

  1. Bounded Context

Bounded Context es la herramienta que nos entrega DDD para acotar los distintos Domains. Una vez estos son identificados a partir de las discrepancias antes descritas, tenemos que trazar un círculo a su alrededor para dejarlos explícitamente identificados, ese círculo acota el Bounded Context. El siguiente paso es identificar el equipo de negocio e IT expertos en ese Domain, en ese Bounded Context y responsabilizarse de la propuesta de valor de la solución para ese problema de negocio.

  1. Context Mapping

Los Bounded Context necesitan interactuar con otros Bounded Context. Comercial no puede estar aislado de mantenimiento, necesitan mandarse mensajes, interaccionar. Context Mapping nos ayudan identificar y especificar las relaciones existentes entre los Bounded Context.

Dichas relaciones cumplirán los patrones de relación que explica DDD y dicho patrón tendrá mayor o menor impacto en la independencia de unos Bounded Context con otros. Mediante la consciencia sobre los patrones de relación entre los Bounded Context, cada equipo puede establecer los mecanismos técnicos – también explicados por DDD – para “proteger” su independencia de otros Bounded Context.

Estos cuatro conceptos, Domain, Ubiquitous Language, Bounded Context, Context Mapping, nos ayudan a dividir nuestra solución en una arquitectura modular en la que hemos entregado correctamente las responsabilidades en base a la realidad del negocio. Y esto implica los siguientes beneficios:

¿Cómo organizar nuestros objetos?

Revisados estos conceptos, movámonos ahora a la parte alta del gráfico. Dicha área se ocupa de la arquitectura del código, ¿cómo organizar nuestros objetos?

  1. Entity & Value Objects

Los Entity son objetos que tienen entidad propia: dos Entities con los mismos valores en sus atributos siguen siendo Entities distintas.

Los Value Objects son imprescindibles para DDD, porque permiten especificar mejor el Ubiquitous Language. La idea es que nos permitan tener objetos (Entities) cuyos atributos sean ricos, que nos permitan huir de los tipos primitivos mediante el empleo extensivo de Value Objects. Además, son inmutables y así deben ser implementados.

ValueObject_A + 1 !== ValueObject_A + 1 -> ValueObject_B
  1. Aggregates

Los Aggregates son pieza clave en DDD. Son agrupaciones de Entities, necesarios para establecer la correcta jerarquía y relaciones entre los Entities para representar la realidad del negocio.

Los Aggregates, además, son los encargados de modelar la persistencia del estado del sistema y, por lo tanto, son los “transactional boundaries”, las barreras de la transaccionalidad. Según DDD las transacciones no deben distribuirse entre distintos Aggregates, lo que nos lleva a la conclusión de que la consistencia del dato en un Aggregate es transaccional, mientras que entre Aggregates es eventual. Asumir esto último nos ayudará a un mejor diseño de nuestro software, a una mejor inter-actuación entre los módulos.

Un Aggregate puede estar compuesto por una o más Entities, siendo una de ellas la principal o root. La Entity root es la que servirá de interfaz entre en mundo exterior al Aggregate y las Entities del Aggregate, cumpliendo así el principio de Encapsulación tan importante para la cohesión del código.

Los Aggregates referencian a otros Aggregate vía su ID, nunca los encapsulan.

  1. Repositories

Los Repository son los componentes que efectivamente llevan a cabo la persistencia de los Aggregates, tanto la escritura como la lectura.

  1. Factories

Las Factories se encargan de la creación de los objetos a partir de los Aggregates, de modo que controlan la lógica de la creación de objetos, la encapsulan.

  1. Commands

Son acciones inmutables que desencadenan un cambio en el estado de uno o varios Aggregates, siempre se enuncian en infinitivo.

  1. Events

Los eventos son hechos inmutables que publican los Aggregates para radiar un cambio de estado que han sufrido. Son imprescindibles para su coreografía. Por convenio se nombran en tiempo pasado.

  1. Services

Son los encargados de implementar el comportamiento de negocio que desencadenan los Commands y para ello se limitan a orquestar las iteraciones entre Aggregates.

Buenas prácticas DDD

Aquí, os traemos un resumen de algunas buenas prácticas como:

En el canal de Youtube de Vaughn Vernon podéis encontrar muy buenas explicaciones sobre DDD desde muchos puntos de vista.

Introducción a Event Storming

Event Storming es una dinámica de grupos, creada por Alberto Brandolini, que persigue conceptualizar un Domain de la mano de los expertos de negocio y de los expertos de IT. Es un proceso iterativo e incremental que busca descubrir y especificar el problema de negocio, sus distintos ubiquitous language y, a partir de ahí, el establecimiento de los bounded contexts, los Agreggates, etc. Todo esto partiendo de los eventos que se producen en el procesos del negocio.

El estándar BPMN describe los procesos poniendo énfasis en sus estados y transacciones, mientras que Event Storming lo hace poniendo en el centro los eventos, la comunicación que se establece entre las distintas entidades del proceso de negocio.

Event Storming puede ser empleado para implementar DDD a nivel macro (Context Mapping de los procesos de una compañía), como también para un nivel no tan extenso (Context Mapping de un proceso de una compañía). Pero no solo queda en niveles relacionados con el Big Picture, también sirve para identificar las partes más pequeñas o indivisibles de un problema: los Aggregates o transaccionalidad que exigen las reglas de negocio.

Es una dinámica cuyo entregable es un enorme mural formado por post-its que especifican los Bounded Contexts, la relación entre ellos, las políticas del negocio, la transaccionalidad, los comandos, los usuarios, la iteración con sistemas externos y, por supuesto, los eventos. Dicho mural muestra, al fin y al cabo, el proceso o procesos de negocio, y la separación de responsabilidades necesaria para la correcta implementación de una solución tecnológica que los soporte.

¿Cómo se hace Event Storming?

El primer paso para una dinámica de este tipo es encontrar una habitación con una pared grande, en la que extenderemos un papel de rollo, para lograr un espacio amplio para nuestros post-its.

En el siguiente paso involucraremos a las personas relevantes de las áreas de negocio que queremos cubrir y a las personas de IT que van a escribir la solución tecnológica.

Es importante, también, acompañarnos de un moderador con experiencia que mantenga las reglas del juego y que mantenga la conversación en el espacio del problema (acordaros que DDD exige describir el Domain sin accidentes tecnológicos), y que no permita durante la dinámica que sesgue el resultado por eventualidades técnicas.

Se hace uso de post-its con el siguiente código de colores:

Una vez se dan estas condiciones, comienza la dinámica, que consta del siguiente proceso:

  1. Los expertos de manera independiente colocan los eventos que se producen en su negocio. Los eventos se colocan en tiempo pasado, cada evento en un post-it naranja (divergencia): “Puntos fidelización añadidos a la cuenta del usuario”.
  2. Se busca el origen de cada evento (convergencia). Este puede ser:
  1. Se revisa la cronología de los eventos (convergencia).
  2. Se revisan las políticas del negocio: “Si ocurre esto más de tres veces, hay que volver a empezar”. Estas políticas ayudan a definir el comportamiento de los Services y de los Aggregates.
  3. Se revisa la transaccionalidad: “Si ocurre A, tiene que ocurrir B. Si no ocurre B, hay que deshacer A”. Esto ayuda a encontrar los Aggregates.
  4. Se repite el proceso.

Este enlace explica en detalle la dinámica.

Onion Architecture

Veamos ahora de qué se trata la arquitectura Onion y cómo se relaciona con DDD. Antes de entrar en detalle, revisemos el concepto de Inyección de dependencia que es clave para comprender cómo funciona dicha arquitectura.

Se trata de un patrón a través del cual el objeto A recibe en tiempo de ejecución el objeto B, del que depende. El objeto o clase A puede especificar el comportamiento que espera del objeto que le es inyectado mediante el uso de interfaces.

Este patrón es ampliamente empleado en la arquitectura Onion, gracias al cual, la arquitectura invierte las dependencias entre capas y con ello logra dos beneficios directos:

Imagen de ejemplo de arquitectura Onion.

La idea fundamental que persigue la arquitectura Onion es invertir las dependencias, de manera que las capas de fuera dependan de las de dentro, y nunca al revés. Al colocar el Domain y los Domain Services en las capas centrales, estas quedan libres de dependencias. Son la capa Application, Infrastructure, Test y UI las que emplean, que dependen de las capas más internas, la de Domain y Domain Services. De hecho, la arquitectura para certificar su correcta implementación sugiere que se compruebe que sea posible compilar paulatinamente las capas desde dentro hacia fuera: solo la Domain, luego la Domain + Domain Services, y, por último, la Domain + Domain Services + Application.

Las capas han de publicar Interfaces para especificar el comportamiento de los objetos que esperan recibir como parámetros desde las capas de fuera en tiempo de ejecución. Lo que ya por sí solo implica que la implementación de las interfaces de las capas de dentro se lleva a cabo en las capas de fuera.

Las capas de dentro se lleva a cabo en las capas de fuera.

Esto nos lleva también a la conclusión de que variando esas implementaciones haremos uso de distinta infraestructura, de distintas capas de aplicación, etc.

La siguiente ilustración muestra cómo los conceptos DDD se reparten en la cebolla propia de esta arquitectura:

Cómo los conceptos DDD se reparten en la cebolla propia de esta arquitectura:

En este enlace podéis encontrar un ejemplo hecho en Python que muestra la implementación basada en Onion Architecture, diseñado mediante Domain Driven Design.

Conclusiones

Hemos revisado los conceptos de Domain Driven Design que nos ayudan a garantizar la prometida adaptabilidad de los microservicios. Además, hemos revisado una arquitectura basada en capas, la arquitectura Onion, que, mediante la inversión e inyección de dependencias, favorece la portabilidad del código.

Si te ha gustado el artículo y te interesa puedes ver este video donde entramos un poco más en detalle sobre estos temas y donde hacemos una demo hecha en Python. El código fuente de la demo lo tienes en este repositorio.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.