Saltar a contenido

Domain Driven Design

Introducción

Domain Driven Design (desde ahora DDD) es un enfoque de desarrollo de software utilizado por Eric Evans en su libro “ Domain-Driven Design — Tackling Complexity in the Heart of Software, 2004”. Representa distintas claves, terminología y patrones utilizados para desarrollar software donde el dominio es lo más central e importante de una determinada organización. Sus principios se basan en: * Colocar los modelos y reglas de negocio de la organización, en el core de la aplicación * Basar nuestro dominio complejo, en un modelo de software. * Se utiliza para tener una mejor perspectiva a nivel de colaboración entre expertos del dominio y los desarrolladores, para concebir un software con los objetivos bien claros.

Hay distintos aspectos que pueden generar la caída de un proyecto, como la burocracia, objetivos pocos claros, problemas técnicos del equipo, problemas de elicitación de requerimientos; pero en software de un dominio complejo y una esperanza de vida un poco más larga puede ser que su caída sea debido a problemas de diseño.

Normalmente, en sistemas Enterprice que necesitan soporte y mantenimiento posterior a la puesta en producción, un mal diseño acortaría su vida útil por no poder comprender las distintas partes o poder extenderse con facilidad. Cuando la complejidad de este dominio no se trata en el diseño, no importará que la tecnología de infraestructura esté bien concebida. Un diseño exitoso debe abordar sistemáticamente este aspecto central dominio del software.

¿Es posible crear software bancario complejo sin un buen conocimiento del dominio? De ninguna manera. Nunca. ¿Quién sabe sobre bancos? El arquitecto de software? No. Simplemente usa el banco para mantener su dinero a salvo y disponible cuando lo necesita. ¿El analista de software? Realmente no. Él sabe analizar un tema determinado, cuando se le dan todos los ingredientes necesarios. ¿El desarrollador? Olvídalo. ¿Quién entonces? Los banqueros, por supuesto. El sistema bancario es muy bien entendido por la gente interna, por sus especialistas. Conocen todos los detalles, todas las capturas, todos los posibles problemas, todas las reglas. Aquí es donde siempre debemos comenzar: el dominio.

Beneficios

  • Comunicación efectiva entre expertos del dominio y expertos técnicos a través de Ubiquitous Languge.
  • Foco en el desarrollo de un área dividida del dominio (subdominio) a través de Bounded Context’s.
  • El software es más cercano al dominio, y por lo tanto es más cercano al cliente.
  • Código bien organizado, permitiendo el testing de las distintas partes del dominio de manera aisladas.
  • Lógica de negocio reside en un solo lugar, y dividida por contextos.
  • Mantenibilidad a largo plazo.

Inconvenientes

  • Aislar la lógica de negocio con un experto de dominio y el equipo de desarrollo suele llevar mucho esfuerzo a nivel tiempo.
  • Necesitamos un experto de dominio —¿ DDD sin Domain-Experts?
  • Una curva de aprendizaje alta, con patrones, procedimientos,…
  • Este enfoque solo es sugerido para aplicaciones donde el dominio sea complejo, no es recomendado para simples CRUD’s.

Aunque seguramente aplicar DDD en un estado puro puede llegar a ser complejo, seguramente se pueden adaptar estos conceptos para utilizar los necesarios o los mas interesantes a la hora de decidir nuestra arquitectura. Hay varios enfoques de aplicación de estos conceptos: Onion Architecture, Hexagonal Architecture, Clean Architecture…

Consultar: Aprende DDD en 20 minutos

Consultar: Design a DDD-oriented microservice

Inicios

Dominio

Es el problema específico que estamos intentando resolver del mundo real. Nosotros tenemos que abstraernos de este dominio, generando una abstracción a través de un modelo. Representa todo el conocimiento de un experto; pero este conocimiento es difícil de plasmar en un desarrollo de software. Un dominio “real” tiene demasiada información que plasmar y es tarea en conjunto la decisión de las abstracciones a lograr. El primer y principal requerimiento de un modelo de dominio es ser consistente y sin contradicciones. * Dominio principal o Core Domain, El diferenciador clave para el negocio del cliente: algo que deben hacer bien y no pueden subcontratar. * Subdominio: separan las características o aplicaciones que su software debe admitir o debe interactuar. Es la segregación de un dominio más general en uno más acotado, cohesivo y comprensible.

El dominio se va a convertir en una capa en nuestra arquitectura, es un lugar central donde nada externo tiene que influir, complicar o distraer la idea principal: el dominio como el corazón de nuestro software. La capa de dominio es la responsable de representar la información del negocio, la lógica del negocio, situaciones del negocio, a pesar de las dificultades que pueden conllevar la infraestructura. Es decir, nos abstraemos de nuestro detalles, que pueden ser que base de datos vamos a utilizar, como vamos a persistir, que framework frontend vamos a utilizar.

Ubiquitous Language

La comunicación efectiva entre los desarrolladores y los expertos del dominio es esencial para el proyecto. Un lenguaje común, que sea representado en el dominio, tanto como en los bounded contexts es muy importante, para evitar tener problemas futuros y desarrollar un software exitoso, donde la comunicación sea el pilar para su obtención. Una incompatibilidad en el lenguaje puede generar traducciones que no son necesarias, e incluso el problema de la traducción ida-vuelta: se traduce algo desde un idioma a otro, y luego cuando se intenta traducir en reversa, tiene otro significado.

Hay que tener en cuenta que los desarrolladores, están acostumbrados a lidiar con términos relacionados a objetos, base de datos, uml, etc; y los expertos de dominio no entienden para nada este lenguaje. Hay que trabajar para encontrar un lenguaje común entre los interesados, por ejemplo en un sistema de veterinaria se puede pensar en: 1. Un Cliente como una persona, dueño de un animal? 2. Un Cliente como un animal, que tiene un dueño persona?

Incluso también se puede ver otra discrepancia en el lenguaje, con respecto a las mascotas que se atienden, estos conceptos pueden ser semejantes según el dominio y el experto del mismo:

  • paciente
  • animal
  • mascota
  • cliente
  • dueño

Es importante definir y tener muy en cuenta estos posibles “fallos” del lenguaje, ambigüedades que pueden generar errores futuros en el desarrollo de un modelo del dominio. En el ejemplo anterior, es difícil definir la brecha entre que es un cliente (un animal o una persona); a su vez esos mismos conceptos utilizados están mal expresados: no es un animal, es una mascota; no es una persona, es un dueño. Entonces, podríamos decir que el modelo es donde debe recaer el lenguaje utilizado, no puede haber discrepancia en lo que se habla y lo que se desarrolla. Tiene que ser un recurso donde todos podamos “hablar” el mismo idioma, podríamos comparar el dominio como un diccionario del Ubiquitous Language. La única manera que tenemos de testear y mejorar nuestro lenguaje del dominio es forzarlo en las conversaciones que tenemos con un determinado experto del dominio, para que afloren dudas y certezas, para asegurarnos que el lenguaje utilizado es el correcto.

Elementos del dominio

Entities

Las entidades son objetos que tienen identidad, normalmente un GUID, UUID o ID. Esto significa que aunque dos objetos de la misma clase, tengan los mismo valores de atributos, no son iguales, sino que la identidad* es quien los identifica como el mismo objeto. Las entidades son capaces de ser buscadas, almacenadas y recuperadas. (track, look it, retrieve and store)

Value Objects

Existen casos en el que un objeto en particular no se comporta como una entidad, no todos los objetos deben ser identificables. En estos casos, las entidades necesitan objetos que tengan un comportamiento bien definido en el que se describe cierto aspecto del dominio. Son llamados Value Objects. Estos objetos describen medidas, cantidad, dinero, etc.

Otras características de los values objects son: * Inmutabilidad: si queremos nuevos valores, no podemos modificar sus atributos, sino crear uno nuevo! Esto nos ayuda a desarrollar sin side-effects. * No tienen identidad, entonces son objetos que pueden ser creados y descartados en cualquier momento. * A nivel implementación, no se debe poder alterar el estado interno y todos los métodos públicos deben comportarse como creacionales, retornando un nuevo objeto con los datos modificados. * La igualdad entre objetos va a estar dada por el valor de sus atributos.

Services

Existe lógica o reglas de negocios que no pertenecen particularmente a un objeto del dominio y no tiene sentido que se encuentre en alguno de ellos. Este comportamiento representa una parte importante del dominio, pero no encaja en ningún value object o entidad. Un ejemplo podría ser la operación de “transferencia” entre dos cuentas bancarias: ese comportamiento ¿a que objeto le pertenece? ¿al emisor? ¿o al receptor? En cualquier lugar que lo situemos, pareciera no encajar con respecto al comportamiento que una cuenta bancaria debe tener. Para estos casos, seguramente necesitemos agrupar esta lógica en objetos particulares, los llamados Servicios de Dominio.

Ocurre que al utilizar estos servicios, estemos programando con interfaces de los mismos y sus implementaciones se encuentre en la capa de infraestructura. Esta diferenciación suele darse así: * los servicios que solo representan lógica de negocio e interactúan con objetos del dominio, se deben encontrar en el core domain o aplicación. * los servicios que interactúan con entidades externas, deben estar en la capa de infraestructura, como por ejemplo: EmailSender, SmsSender, ReporterPrinter.

Aggregates

Una agregación tiene una sola raíz y todas las operaciones deben realizarse a través de ella. Esta raíz es una entidad, puede tener referencias a los objetos agregados, pero ningún objeto del exterior puede tener referencias a los objetos dentro de la agregación, toda operación debe ocurrir por la raíz. La existencia de la agregación se da en una sola dirección, si la raíz es eliminada, todo los objetos agregados a ella también deberán ser eliminadas, no tienen sentido de existencia si la raíz deja de existir. La recuperación se debe realizar siempre a través de las raíces, aunque necesitemos un dato que se encuentra dentro de los agregados. Las agregaciones aseguran la integridad de datos y fuerzan las invariantes. Cualquier cambio que se intenta realizar, debe ocurrir desde la raíz, y desde allí podemos forzar las invariante entre los objetos.

  • Deben respetar la regla de cascada, si se elimina la raíz todos los objetos agregados deben eliminarse
  • Solo se puede interactuar con los objetos agregados a través de la raíz.
  • Las agregaciones puede contener un solo objeto, o una lista de objetos y nada más. (como el planificador y su la lista de turnos)
  • Deben respetar las invariantes desde la raíz
  • Solo las raíces van a tener Repositorios, las persistencia siempre se va a dar por alcance o cascada para los objetos agregados.

Factories

Puede ocurrir que la construcción de Entidades y Agregaciones sea demasiado compleja. Y en realidad, lo que nos interesa es la utilización y no tanto la fabricación del mismo: es como cuando utilizamos cualquier electrodoméstico, solamente lo usamos y delegamos la fabricación en algún señor que se dedique a eso. Ocurre lo mismo con las Entidades y Agregaciones, internamente puede ocurrir que sean estructuras complejas, y conocer estructuras internas para su fabricación sería violar el principio de encapsulamiento. Por ende, vamos a delegar la fabricación en un objeto Factory. Este objeto, tendrá la responsabilidad de fabricar objetos complejos, centralizando el conocimiento de la fabricación (y que no quede desperdigada por todo nuestro código), mejorando la calidad del mismo, generando código testeable.

Repositories

Las Entidades desconocen completamente su forma de persistirse, no tienen ese conocimiento ni tampoco le interesa: solo debe interesarse por cumplir sus invariantes y las reglas de negocio. Toda la lógica, debería estar encapsulada en los Repositorios. Cada Aggregate Root debería tener asociado un repositorio, así como las entidades que no se encuentran dentro de agregaciones. En si, un repositorio es similar a una colección de objetos pero que tiene capacidad de elaborar consultas un poco más avanzada.