Domain Driven Design

Foto

Bueno, este es mi primer post así que voy a aprovechar para presentarme. Soy Javier Calvarro Nelson, estudiante de 5º de informática de la universidad de Málaga, llevo varios años trabajando con .NET. He participado en imagine cup en 2009 quedando en 2º lugar y he trabajado como becario en Microsoft en DPE Arquitectura.

Tras el autobombo, voy a hablar de Domain Driven Design. Lo primero que hay que decir sobre esto es que si buscas en internet vas a encontrar 15 formas distintas de definirlo y de implementarlo, aunque todas ellas tienen bastantes puntos en común. Dicho esto voy a citar a continuación mis referencias para el que quiera saber más.

En primer lugar un artículo de MSDN magazine en el que está basado en gran medida este post http://msdn.microsoft.com/en-us/magazine/dd419654.aspx, en segundo lugar un par de libros que lo ilustran bastante bien http://www.amazon.com/dp/0321125215/ref=rdr_ext_sb_ti_sims_1 que es prácticamente la biblia del DDD y http://www.amazon.com/NET-Domain-Driven-Design-Solution-Programmer/dp/0470147563#noop que es un libro que mezcla teoría e implementación. Iré ilustrando todo el ejemplo con una “aplicacion teórica” de gestión de cuentas bancarias. Bueno, dicho esto vamos al grano.

Domain Driven Design son varias cosas. En primer lugar es una forma de diseñar el software centrándonos en lo que el cliente nos pide. El software que hacemos tiene como objetivo resolver un problema de nuestro cliente. Este problema está contextualizado dentro de un dominio, es decir, el cliente maneja una serie de datos, relaciones y operaciones  como por ejemplo, cuentas, movimientos, transacciones, etc, y nuestro software debe reflejar eso en su estructura. Cuando realizamos una aplicación siguiendo Domain Driven Design es importante que nos centremos en entender y resolver el problema de nuestro cliente, es decir, debemos tener un conjunto de clases y operaciones asociadas que resuelvan el problema de nuestro cliente y que sean independientes de cualquier otro aspecto del sistema como la persistencia, exposición como servicios web, etc.

Uno de los principales problemas en el desarrollo software es la comunicación con el cliente. Los clientes hablan un idioma, el idioma de su dominio, es decir cuentas, transacciones, IRPF, IVA, etc. Los desarrolladores por otra parte no entendemos ese lenguaje y si hablamos otro lenguaje con términos como servicio web, persistencia, capa de negocio, etc. Por esto, cuando nos comunicamos con el cliente no somos capaces de entender bien su problema y por tanto no podemos resolverlo bien. Lo que propone Domain Driven Design es que los desarrolladores se empapen del dominio del problema de su cliente, es decir, conozcan los términos del dominio y su significado y en base a ello acuerden con el cliente un lenguaje común para hablar. Los desarrolladores pueden proponer términos, pero siempre es el cliente el que decide.

En segundo lugar, Domain Driven Design es un estilo arquitectural. Se parece bastante a un estilo en N-Capas (N-Layer) en tanto que los dos estilos separan las responsabilidades de la aplicación, pero la forma de hacerlo es ligeramente distinta. Este es un diagrama de capas de una arquitectura Domain Driven a mi entender, perdon por la “belleza del mismo” pero no soy todavía un experto en el layer diagram de VS 2010.

DDD

Comenzaremos por la capa de Dominio. La principal responsabilidad de esta capa es representar y resolver el problema de nuestro cliente, aquí definimos el modelo de dominio del cliente (Entidades de dominio), las operaciones comunes en el dominio del cliente que no están asociadas a ninguna entidad concreta del modelo de dominio (Servicios de dominio) y los datos que puede requerir la aplicación (Repositorios) con esto nos referimos a cosas del estilo dame la cuenta nº xxxxx y cosas así.

Entidades de dominio: Una entidad de dominio es la representación de un concepto del dominio dentro de nuestro sistema y de las operaciones que se pueden realizar sobre él. Una entidad tiene una clave que es única y la diferencia de cualquier otra entidad. Las entidades de dominio no son simples clases de datos, la forma más correcta de ver una entidad es como una “unidad de comportamiento”, haciendo las cosas de esta forma nos beneficiamos del principio de encapsulación y evitamos el anti-patron Anemic Domain Model. Por ejemplo, una entidad podría ser la clase Cuenta que se compondría de Nº de cuenta (Clave), Titular, y un conjunto de movimientos (Ingresos, Cobros, etc) y métodos como SaldoActual que a partir de los movimientos obtendría el saldo de la cuenta o métodos como congelar que impediría realizar más operaciones sobre la cuenta.

Value Objects: Los value objects se utilizan para representar conceptos importantes en nuestro dominio, pero su proposito es muy distinto al de las entidades. El objetivo de los value objects es describir un concepto importante para nuestro dominio como podría ser “Dinero”. Los value objects no son value objects de .NET y tampoco son entidades por que no tienen una clave. De hecho, los objetos por valor deben ser inmutables, es decir, cualquier intento de modificarlo debe producir un elemento nuevo y no modificar original (Como la clase String) de forma que los podamos exponer tranquilamente desde nuestras entidades y al mismo tiempo evitemos efectos laterales en las mismas. (Devuelvo una referencia al objeto, alguien modifica algo y queda modificado en mi entidad violando el principio de encapsulación).

 

Si pensamos un poco, hacer las cosas así puede hacer que la complejidad del sistema se nos dispare. Por ello, existe el concepto de agregados. Cuando definimos un agregado establecemos una jerarquía entre entidades en la que una entidad denominada “raíz” se encarga de controlar al resto de entidades del agregado. Podemos identificar una relación de agregación entre entidades cuando nos encontramos en la situación de que no tiene sentido que exista una entidad por sí misma si no está relacionada con otra. En el ejemplo, las cuentas y los movimientos formarían un agregado en el que la cuenta sería la raíz del mismo y los movimientos serían entidades dependientes.

Servicios de dominio: Típicamente en nuestro dominio van a aparecer operaciones que no encajen bien dentro de ninguna entidad ya sea por que la operación tenga entidad por sí misma o por que la operación involucre a multiples tipos de entidades. En ese caso, esa operación se extrae a un servicio de dominio. En nuestro ejemplo, un servicio de dominio podría ser por ejemplo una transacción. Los servicios de dominio solo deben ser utilizados para orquestar las operaciones entre entidades y no deben jamás tener estado interno.

Ahora que ya hemos visto los principales componentes de la capa de dominio, vamos a tratar un tema algo más complicado. A menudo, nuestras operaciones de dominio tendrán que realizar cosas como auditoría de la operación, enviar un correo, etc. Nuestro objetivo con DDD es conseguir un modelo lo más independiente del resto de la aplicación. Por ello, a la hora de realizar este tipo de operaciones la forma de proceder es crear interfaces que desacoplen este tipo de operaciones. Tendríamos así por ejemplo interfaces tipo ILogger o IEmailer, y tendríamos dos formas de usarlos. La primera consistiría en pasar estas operaciones como parámetros del método de la entidad o servicio de dominio. Por ejemplo, transactionSvc.Transacción(Cuenta c1, Cuenta c2, Money cantidad, ILogger l) y la segunda sería crear un servicio de dominio donde se reciben este tipo de elementos en el constructor y una operación de servicio se encarga de hacer la combinación de las operaciones. Por ejemplo, crearíamos un servicio TransactionProcessor donde realizaríamos la transacción y después llamaríamos a ILogger.log(transaction). Podríamos haber pasado al servicio de transacciones directamente el ILogger, pero en ese caso estaríamos comenzando a violar el principio de Single Responsability ya que una transacción tiene que ser simple y llanamente una transacción, no tiene que ocuparse de auditarse o enviar un mail, etc. Tenemos que tener claro que el dominio tiene que estar lo más desacoplado posible del mundo exterior para facilitar su testeo y su reutilización.

Repositorios: Un repositorio es una colección de objetos, o así es como debe verse para su utilización. En la capa de dominio definimos las interfaces que nuestro nivel de aplicación va a utilizar para para instanciar entidades de nuestro dominio pero no su implementación, esta se delega en la capa de infraestructura. Típicamente en estas interfaces encontramos métodos del tipo GetAccount(int accId) o  Add/Remove/Update(Account ac).

Con esto queda más o menos explicada la capa de dominio, espero que se entienda. La siguiente pregunta que toca es “vale, ya tengo resuelto mi problema, pero ¿Cómo hago una aplicación con esto?” Para eso están las otras capas.

Capa de Infraestructura: Esta capa es la responsable de implementar el mecanismo de persistencia del modelo de dominio y de proporcionar la implementación de todas las operaciones de comunicación, auditoría, etc.

La capa de infraestructura implementa las interfaces de los repositorios definidas en la capa de dominio para el mecanismo escogido (ficheros, base de datos, etc).  y todas aquellas operaciones de comunicación con el mundo exterior que necesite el dominio (Emailer,Logger, etc.). El mecanismo escogido para la persistencia debe ser transparente a la capa de dominio.

Capa de Aplicación: Esta capa sirve de “pegamento” para el sistema. La responsabilidad de esta capa es “controlar” o manipular el dominio. Los componentes de esta capa están directamente orientados a dar implementación a los casos de uso o historias de usuario. Por ejemplo, dado un titular crearle una nueva cuenta o realizar una transferencia desde la cuenta con id1 a la cuenta con id2. En esta capa, los componentes emplean los repositorios del dominio para traerse los objetos del almacenamiento persistente, instanciar los servicios de dominio y otros componentes necesarios, realizar las llamadas adecuadas para que el modelo de dominio resuelva el problema y finalmente salvar los cambios.

En el ejemplo de la transferencia podríamos tener una clase de servicios de aplicación llamada GestorDeCuentas con métodos del tipo CreateNewAccount(int idUsuarioCuenta) o TransferMoney(int account1, int account2, int ammount). Dentro de este último método por ejemplo, se traerían las 2 cuentas mediante los repositorios, se instanciaría una transacción, un TransactionProcessor y un logger y se realizaría la llamada. Por supuesto esto se simplifica bastante si usamos algún IoC como Unity.

En esta capa también crearíamos interfaces e implementaciones para servicios, DTOs etc en caso de que fuese necesario.

Capa de presentación: No voy a hablar prácticamente nada de esta capa puesto que es prácticamente igual que la capa de presentación de un estilo N-Capas. Simplemente en esta capa controlaríamos la acción del usuario y mostraríamos los datos que nos pide.

Bueno, esto ha sido todo por hoy. Espero que os guste el post y comenteis aquello con lo que no esteis de acuerdo, que al fin y al cabo esto está para que discutamos. Se me han quedado algunas cosas en el tintero como los bounded contexts, pero viene explicado en el artículo de MSDN magazine.

En el siguiente post una pequeña implementación de ejemplo.

Un saludo,

Javier.

9 comentarios en “Domain Driven Design”

  1. Javier bienvenido y que pedazo de Post !!! claro ahora que OSLO lo teneis cerquita y que en VS 2010 los DSLs han madurado es mucho más facil hablar de DDD jejeje … siendo un poco más serio: excelente post … espero el proximo

    Saludos

  2. Bienvenido! Muy buen post y cuanta razón tienes en que no nos entendemos bien con el idioma del dominio del cliente! Espero que nos enseñes cómo resolver esto con VS2010 😉 te leo en el próximo

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *