jueves, 31 de enero de 2013

Arquitecturas para Domain-Driven Design

Este es el primer post de una serie que tratará, de forma introductoria, sobre Domain-Driven Design (DDD) -que en castellano sería algo como Diseño Guiado por el Dominio- y el diseño de una arquitectura subyacente que cumpla con los requisitos del dominio, manteniendo los principios SOLID en el diseño de las clases y un bajo acoplamiento de los módulos. La arquitectura esta orientada a grandes aplicaciones de gestión con complejas reglas de comportamiento. 

El objetivo de una arquitectura basada en Domain-Driven Design es conseguir un modelo orientado a objetos que refleje el conocimiento de un dominio dado y que sea completamente independiente de cualquier concepto de comunicación; ya sea con elementos de infraestructura como de interfaz gráfica, etc. Buscamos construir un modelo a través del cual podamos resolver problemas expresados como la colaboración de un conjunto de objetos.

Mi objetivo no es que se aprenda DDD en esta serie, si no aclarar como es traducido esto en la vida real. Yo me he encontrado muchas veces leyendo teoría muy bonita, con unos párrafos muy bien construidos, y he pensando: Buena exposición pero ¿Cómo llego, partiendo de esto, a tener una arquitectura y un código fuente decente?

Empezemos con el...

Lenguaje Ubicuo.

Cualquier modelo que construyamos debe representar de forma explícita los principales conceptos del dominio de conocimiento con el que trabaja nuestro sistema. Debemos fomentar la construcción de un lenguaje de uso común, tanto entre expertos del dominio y desarrolladores, como entre los propios desarrolladores, que contenga los principales conceptos del dominio de conocimiento, que además sea el lenguaje usado para expresar cómo se resuelven los distintos problemas y objetivos del sistema.

Como decía, un párrafo muy bonito, pero ¿A dónde se quiere llegar?. La principal consecuencia de utilizar un lenguaje común para el dominio es que se facilita la comprensión del dominio por parte de los desarrolladores y la comunicación entre todos los implicados en el desarrollo. Otra consecuencia menos obvia, pero igual de importante, es que los desarrolladores se esforzarán por diseñar un modelo cuyo nivel de abstracción sea lo suficientemente alto como para que un experto del dominio pueda leer el código fuente de una actividad del modelo y sea capaz de entenderlo.

Pongo un ejemplo de esto para que se entienda mejor:

Una de las actividades de nuestro sistema bancario es realizar transferencias entre cuentas corrientes. La información que tenemos del experto del dominio es la siguiente:
Para realizar una transferencia de cierta cantidad entre cuentas, la cantidad tendrá que ser mayor que 0. Se comprobará que la cuenta de origen no esté bloqueada. Si no está bloqueada se comprobará que en el balance tiene saldo positivo suficiente o que se permite saldo negativo hasta ese punto. En caso de cumplirse las condiciones anteriores, se sustraerá la cantidad de la cuenta origen y se agregará a la cuenta destino.
Al más alto nivel de abstracción de nuestro modelo de dominio deberíamos tener el siguiente código:

function TransferirSaldo(ctaOrigen, ctaDestino, cantidad)
{
    //no se puede transferir 0 o cantidad negativa
    If (cantidad < 1) {error, incumplimiento de reglas de dominio}

    //Si la ctaOrigen está bloqueada o no tiene saldo suficiente 
    //(ni crédito para llegar a los números rojos con los que
    //resultaría en caso de la transacción monetaria) la entidad ctaOrigen
    //lanzaria una exception de reglas de negocio que dejariamos fluir hacia
    //arriba.

    //Resta la cantidad del balance de ctaOrigen
    ctaOrigen.sustraer(cantidad)

    //Agregar la cantidad al balance de la ctaDestino
    ctaDestino.agregar(cantidad)

}

Si tienes un diagrama de flujo que representa esto te darás cuenta que es clavadito clavadito.

Millares de ventajas.

Esto lo lee el profano en programación que te ha contado como funciona el proceso de transferencias ente cuentas y lo entiende perfectamente (y puede corregirlo en caso de no ser correcto o completo). Nosotros, como desarrolladores, también podemos ver con mucha facilidad si hemos modelado la secuencia de actividades correctamente, si nos hemos saltado algún paso, si el orden no es correcto... etc. Un programador recién llegado puede montar un nuevo proceso leyendo la definición del proceso y utilizando los métodos de las entidades que, por sentido común, corresponden de una forma inequívoca a los pasos de la definición. Las responsabilidades están en su sitio y no es necesario repetir código. Si el estado binario de una cuenta ("bloqueado" o "no bloqueado") pasa a ser un enumerado (digamos "bloqueado", "bloqueado para sacar dinero" y "bloqueado para ingresar dinero") ¿Se imaginan ustedes si tenemos en 30 sitios desperdigados por el código una línea como ésta, en la que hay que cambiar un booleano por un enumerado? if (ctaCorriente.bloqueado = true)...  Se montó el belén...

En resumen, utilizar un leguaje ubicuo no sólo facilita la comunicación y la comprensión del dominio; además nos hace conscientes -a los desarrolladores- del nivel de abstracción y diseño de entidades y negocio al que debemos llegar, y nos da unos objetivos concretos que cumplir cuando empezemos a diseñar el sistema desde los cimientos.

6 comentarios:

  1. No había leído nada sobre DDD, pero por lo que cuentas parece bastante razonable usar un lenguaje familiar al experto.

    A niveles altos de una arquitectura bien estructurada es fácil y aunque en niveles 'menos altos' pueda complicarse un poco la cosa, creo que mientras el código maneje lógica de negocio puede lograrse un código limpio como el del ejemplo, siempre que te den tiempo para hacer las cosas como es debido.

    ResponderEliminar
  2. Buena entrada, a ver si nos convertimos en "referente mundial" de la Arquitectura de Software. :D

    ResponderEliminar
  3. Ya lo creo puede complicarse. El resto de post de la serie irán por ese camino.

    Lo importante es remarcar que aunque un software se empieza desde abajo y se va subiendo el nivel de abstracion, hay que tener presente y muy claro como va a ser el resultado final. Utilizar un lenguaje comun ayuda mucho.

    ResponderEliminar
  4. Empezando a leer tus post sobre DDD.

    Saludos desde Santa Cruz, Bolivia.

    ResponderEliminar
  5. Hola Nenaza,
    Disculpa el detallismo
    Esto:
    //no se puede transferir 0 o cantidad negativa
    If (cantidad < 1) {error, incumplimiento de reglas de dominio}
    Deberia ser esto:
    //no se puede transferir 0 o cantidad negativa
    If (cantidad <= 0) {error, incumplimiento de reglas de dominio}

    Saludos

    ResponderEliminar
    Respuestas
    1. Hola Manuel. Para este ejemplo tonto estoy asumiendo valores enteros ya que como no hablo del tipo de moneda que se gestiona no existen fracciones ni nada parecido. Para este caso, creo ambos operadores de comparacion funcionan igual. En caso de que se me escape algun detalle no dudes en comentar de nuevo.

      Eliminar