Advertisement
  1. Code
  2. Designing

Diseño basado en el dominio

by
Read Time:20 minsLanguages:

Spanish (Español) translation by Esther (you can also view the original English article)

En mi país, no se pasa por la escuela sin leer cómo se queja Fausto, de la obra literaria de Goethe, ¡Ay, he estudiado ya Filosofía, Jurisprudencia, Medicina, y también, por desgracia! Teología, ¡Todo ello en profundidad extrema y con enconado esfuerzo! Y aquí me veo, pobre loco.

Lamentablemente, ninguno de sus esfuerzos y estudios ayudó al doctor a percibir lo que mantiene unido al mundo en sus pliegues más íntimos.

Y aquí estamos en la informática: hemos estudiado lenguajes y frameworks, librerías e incluso, por desgracia, ¡la informatica! Todo a través de un ardor entusiasta. Pero, ¿cuántas veces nos hemos centrado en lo que mantiene la aplicación en sus pliegues más íntimos? El tema de hoy es el ámbito empresarial.


Lógica empresarial y diseño de software

A veces se considera que la lógica empresarial es única, ¡y lo es por definición! Si la lógica empresarial de una aplicación no fuera única, no habría necesidad de escribir una aplicación, puesto que ya existe una solución (con la excepción de cuando una aplicación existe pero no está disponible). De ahí que muchos desarrolladores se vean a sí mismos como pioneros, para ir con audacia donde nadie ha ido antes. Dejando a un lado los romanticismos, si bien la lógica empresarial en sí puede ser única en un grado notable, las técnicas para implementarla no lo son. Por eso se invitó a modelos de procesos inteligentes como Rational Unified Process o Scrum, junto con técnicas como los ciclos de desarrollo iterativos e incrementales. Arquitectos de software de gran talento han elaborado también enfoques para el diseño de software; entre ellos Eric Evans, que acuñó el término Domain Driven Design (Diseño basado en el dominio) en su libro del mismo título.

Los desarrolladores van con valentía, donde nadie ha ido antes.

Daré una visión general de cómo el Diseño dirigido por el dominio puede influir en el proceso de consultoría, así como sus conceptos básicos para diseñar un modelo de dominio. Por último, hablaremos de los requisitos de infraestructura necesarios para implantar un dominio con facilidad.

Requisitos de ingeniería

Digamos que eres un arquitecto de software en una aplicación no trivial con un dominio no trivial, como el motor central de una gran empresa de logística. Muchas personas se suman a las charlas de planificación, entre ellas gestores de proyectos, gestores de cuentas, marketing, consultores, etc. No todo el mundo es necesario para hacer el trabajo (no compartiré mis opiniones sobre a quién se aplica esto), pero dos personas desempeñarán un papel crucial durante el proceso de ingeniería de requisitos: tú, el arquitecto, y el experto en el dominio.

Un arquitecto de software (al menos en el contexto empresarial) debe tener una muy buena comprensión abstracta de cómo funcionan los procesos, cómo se diseñan y optimizan.

Eso es cierto porque las aplicaciones empresariales consisten principalmente en diseñar equivalentes digitales eficientes y bonitos de los procesos empresariales. Un experto en el dominio debe tener un conocimiento profundo sobre un conjunto específico de procesos, es decir, los procesos que tienen lugar en la empresa de logística y que deben ser reflejados por la aplicación. He comprobado que los consultores empresariales, el director de ventas y los expertos en marketing hacen algunos puntos buenos y valiosos en el camino, pero mientras no haya alguien en el equipo que se haya ensuciado las manos con años de experiencia, el proyecto fracasará. Por ejemplo, el experto en la materia debe saber la anchura de la rampa de carga del depósito y si hay espacio suficiente para instalar un escáner de código de barras.

Así que eres tú, especializado en procesos empresariales digitales, diseño de software y [rellena aquí tus herramientas favoritas], y un experto en logística con conocimientos sobre los clientes de la empresa, los empleados y el día a día. Lo más probable es que hablen en sentido contrario. El Diseño dirigido por el dominio sugiere algunas estrategias que pueden conformar un potente servicio de consultoría técnica. Aquí está el mío:

  • Crear un lenguaje de ubiquitos
  • Construir un glosario de palabras clave
  • Pasar de una visión orientada al proceso a un enfoque centrado en el dominio.
  • Construir un modelo visual como base de tu lógica empresarial.

¡Suena divertido! Entremos en los detalles.

En cada sector, cada grupo de expertos tiene su propia terminología. Se perfecciona en cada empresa y se enriquece con los términos especiales de las empresas y los nombres de los productos. Piensa en la informática: cuando gente como nosotros se reúne para hablar en serio de frikismo, ¿quién más podría entender una palabra? Lo mismo ocurre con tu dominio, y lo primero que hay que hacer es definir un conjunto de términos. Recorre todo el conjunto de procesos que debe reflejar el software y escucha atentamente cómo lo describe el experto en la materia. Los términos específicos del dominio deben definirse de la misma manera que los diccionarios. Debes estar atento a las palabras que te suenan pero que no están en el contexto dado. Algunas empresas nunca han hecho ese trabajo, aunque sea valioso para otras áreas.

Haz un glosario dedicado a tus términos habituales, asegúrate de que lo apruebe el cliente y cobra por el proceso de consulta. Un glosario puede tener este aspecto:

Un extracto de un glosario.

Observa cómo un glosario bien definido ya establece dependencias y asociaciones. Al igual que la orden, que alberga múltiples artículos. ¡Seguramente tendrás clases para esos en tu lógica empresarial! Tu clase Order tendrá presumiblemente un método como getItems(). Sin tener en cuenta las técnicas de programación, un glosario puede sentar las bases de tu modelo de dominio. Además, estás construyendo un lenguaje que se utiliza a lo largo de todo el proyecto: en los correos, en las reuniones y, ¡definitivamente, en el código! Tu código debe reflejar el dominio; por lo tanto, debe estar definido en el glosario. Aquí tienes una regla general: ¡siempre que crees una clase que no tenga el nombre de una entrada del glosario, es posible que tu lenguaje habitual aún no esté suficientemente definido!

Al amparo de la oscuridad, ¡hemos cambiado la visión de los requisitos! Normalmente, un cliente describe lo que debe hacer el software que se va a escribir. Una descripción típica podría ser: "Necesitamos una forma de añadir notas a un cliente e imprimirlas". Es un buen punto de partida, pero no se centra en el ámbito empresarial. Introduce algún tipo de interfaz de usuario, funcionalidad de impresión, e incluso más. Seguramente lo necesitarás en tu aplicación, pero no forma parte del dominio. Diseño basado en el dominio se centra en el modelado del verdadero propósito de una aplicación: el dominio del negocio.

Todo lo demás debería surgir de ahí, una vez hecho el dominio. El cambio es el siguiente: no implementar procesos, y construir un dominio puro que refleje las necesidades de los clientes en objetos. Una forma de visualizar la descripción del cliente superior sería así (los getters y setters solo se añaden cuando son necesarios para el entendimiento):

Un diagrama sencillo que refleja el dominio requerido.

Ahora tenemos un conjunto de clases y asociaciones que no hacen más que reflejar las definiciones de nuestro glosario. ¿Es este modelo capaz de realizar las tareas necesarias? Por supuesto, necesitarás un PrinterService y una interfaz de usuario en alguna parte de tu aplicación, pero solo tienen que tomar algunos datos del dominio. No son necesarios en este momento, y su aplicación no será decisiva para el resultado.

La filosofía de DDD se basa en la suposición de que una capa de dominio cuidadosamente diseñada puede realizar todos los procesos necesarios con facilidad. Un modelo de dominio es escalable, ya que no se construye para satisfacer una tarea determinada, sino para reflejar un concepto de negocio. Es intercambiable, ya que no está vinculado a ningún software específico, ni siquiera a una interfaz de usuario. Puedes utilizar el mismo modelo en el escáner de código de barras de la rampa de carga del depósito. Como veremos en el próximo capítulo, ni siquiera está vinculado a otros componentes que están construyendo tu aplicación.


El modelo de dominio

En uno de mis últimos artículos, escribí sobre la aplicación del principio KISS.

En uno de mis últimos artículos, escribí sobre la aplicación del principio KISS: la mayoría de los sistemas funcionan mejor si se mantienen simples en lugar de hacerse complejos. Pues bien, cuando se trata de implementar un dominio basado en la filosofía de DDD, se puede encontrar un enfoque bastante radical en el mundo moderno de los frameworks, patrones y disciplinas; como por ejemplo, implementar un objeto ordinario en el lenguaje llano de tu elección. Sin dependencias del framework, sin convenciones de bibliotecas, sin rastros de ninguna API, sin nombres extravagantes. Un simple objeto (como un concepto no se toma en serio sin un nombre elegante en el mundo de Java, allí tienen uno).

Entidades vs. Objetos de valor

Cuando queremos reflejar el modelo de dominio, un punto crucial es definir su estado. En la programación orientada a objetos, el estado de un objeto se define por el estado de sus propiedades. Asimismo, el estado del modelo de dominio se define por el estado de sus objetos. Por lo tanto, debemos tener una forma de definir claramente el estado de los objetos. Si no pudiéramos hacerlo, fracasaríamos en casos de uso fáciles como "¿Cuántos pedidos hay?". porque la respuesta siempre requiere conocer el estado de todos los objetos Order en un dominio y una forma de identificarlos y distinguirlos. DDD define dos tipos de objetos: Las entidades y los objetos de valor.

Una entidad es un concepto conocido, si está familiarizado con las bases de datos relacionales.

Las tablas de una base de datos relacional suelen tener un identificador único que distingue una fila de otra. Lo mismo ocurre con las entidades. Una entidad debe tener un identificador claro que sea único en todo el sistema. En el caso de un pedido, podría ser una propiedad de tipo uint, denominada orderNumber. Por supuesto, se buscaría en el glosario, donde debería estar definido el término correcto.

Una entidad permanece igual cuando algunas propiedades cambian. Por ejemplo, puede añadir o eliminar artículos de un pedido, pero sería el mismo pedido. ¿Qué ocurre cuando cambias el orderNumber? Pues bien, desde el punto de vista de tu dominio, se elimina una orden y se crea otra.

Un objeto de valor es un simple contenedor de información. Es inmutable una vez creada. Cambiar una propiedad significa que cambiarías el objeto de valor. Un objeto de valor se define por todas sus propiedades; no necesita un identificador único. Todo el objeto es uno. Un ejemplo de objeto de valor sería un OrderAddress, ya que está definido por el nombre, la dirección y la ciudad del destinatario. Si se cambiara una propiedad, por ejemplo la ciudad, el OrderAddress cambiaría completamente.

Si cambias una propiedad de un color, será otro diferente.

Dividir los objetos en objetos de valor y entidades es importante para definir el estado de su dominio, ya que es la base para identificar los componentes. Pero también es importante definirlos para tener un dominio escalable y mantenible. Las entidades son la representación de objetos del mundo real como Personas, Pedidos o Artículos. Los objetos de valor son contenedores de información como colores o direcciones y son reutilizables y compartibles entre entidades o incluso entre todo el sistema. Definirlos puede requerir algo de práctica, ya que depende del caso de uso el tener un objeto de valor o una entidad.

Asociaciones, agregados y repositorios

Cuando echamos un vistazo al resumen de nuestro glosario, podemos ver las conexiones y dependencias entre nuestros objetos en la capa de dominio. En DDD, esto se llama asociaciones y es el modelo de las interacciones que están teniendo lugar.

Por ejemplo, los artículos forman parte del pedido. Si procesáramos contra una base de datos relacional, esto sería una relación de uno a muchos (o 1:n). Si cada pedido tuviera exactamente una OrderAddress, sería una relación uno a uno. Como no nos interesan las bases de datos relacionales y solo nos interesa terminar el dominio, la relación se puede expresar fácilmente con dos métodos en la clase Order: getItems() y getOrderAddress(). Observa que el primero es plural (ya que hay muchos artículos) y el segundo es singular. Si tuvieras una relación de muchos a muchos, darías a ambas clases un método getter. Por supuesto, también necesitas setters; los he omitido para que los ejemplos sean ligeros.

Los artículos y la dirección del pedido pueden verse como hijos del pedido.

En DDD intentamos evitar las relaciones de muchos a muchos, ya que tienden a añadir complejidad al dominio. Técnicamente significa que dos objetos tienen que mantenerse sincronizados durante su ciclo de vida, y mantener las cosas sincronizadas puede llevar a la violación del principio DRY. Por eso, el proceso de perfeccionamiento del modelo debe buscar la simplicidad. En muchas ocasiones, una asociación es más fuerte en una dirección que en la otra, y es una buena idea rediseñar la estructura a una relación de uno a muchos. Comprueba si la asociación es relevante para la lógica empresarial de tu aplicación. Si solo se produce en casos de uso no esenciales y poco frecuentes, es posible que quieras buscar otra forma de recibir la información necesaria.

Las asociaciones construyen un árbol de objetos y deberías terminar con una construcción de asociación donde cada objeto puede ser recuperado a través de métodos getter. Se trata de una construcción padre-hijo que en última instancia conduce a un objeto raíz. Esto se llama agregación en DDD. Un buen diseño conduce en última instancia a un agregado que es capaz de reflejar todo el dominio. De momento solo hemos mirado una pequeña parte de nuestro glosario, pero parece que un cliente es nuestro agregado raíz:

El objeto cliente es el padre de todos los miembros del dominio.

Los agregados son una parte importante, ya que DDD trata de aislar el dominio de la aplicación circundante. Si queremos tener información sobre un cliente, pedimos un agregado raíz y podemos recorrer sus hijos para acceder a la información a través de una interfaz clara de getters y setters.

El DDD es como las ventas, proporciona una cara al cliente, el agregado al sistema circundante. Por lo tanto, da acceso a un conjunto estructurado de información y métodos relevantes para los procesos; por ejemplo, la orden.

El Diseño dirigido por el dominio es como las ventas, proporciona una cara al cliente.

La aplicación circundante accede a un agregado a través de repositorios, que son básicamente una especie de fachada. En otras palabras: Un objeto de dominio es un agregado si tiene un repositorio. Los repositorios proporcionan métodos para consultar los agregados. Los ejemplos pueden ser findClientByEmail(string email) o simplemente findAll(). También realizan actualizaciones y añaden nuevos objetos al dominio. Así, es probable que tengan métodos como add(Client newClient) o delete(Client toBeDeletedClient).

Con un agregado accedes a los hijos solo a través de su padre. Por ejemplo, un agregado de clientes le da acceso a todos los pedidos del cliente. Pero si necesitas acceder a los datos desde una perspectiva distinta a la del cliente, puedes establecer un segundo agregado. Supongamos que quieres tener una lista de todos los pedidos, independientemente del cliente que los haya realizado. ¡Un repositorio de pedidos hará el trabajo!

La capa de dominio y sus repositorios.

Dado que el repositorio es el punto de entrada de la aplicación circundante a la capa de dominio, es donde otros actores entran en la zona. Recuerda que por ahora estamos tratando con objetos planos.

¿Te has preguntado cómo se hará realidad? Aquí es donde entran las infraestructuras. DDD es un excelente compañero para los frameworks, ya que está construido sobre objetos simples, con un esquema sencillo de objetos de valor, entidades y agregados. Sin embargo, como la simplicidad es el poder en TI, ahora estamos en condiciones de externalizar toda la parte de la infraestructura. Echemos un vistazo bajo el capó, y cómo DDD puede extenderse por una aplicación.


Infraestructura y capa de dominio

Habrás notado que nuestro enfoque en el dominio excluyó una capa de persistencia así como cosas comunes como vistas o controladores de nuestra lista de cosas por hacer. La aplicación completa puede consistir en cosas mucho más complejas que simples objetos, y quiero señalar algunos pasos que hay que hacer para conectar el dominio y la aplicación, así como las estrategias de implementación que existen. Haré algunos ejemplos basados en FLOW3, un marco de aplicación con el objetivo principal de proporcionar una infraestructura DDD. No es necesario, pero no estará de más que leas mi introducción. Para aplicar el dominio de negocio a una aplicación, los siguientes pasos son comunes:

  • Implementar una capa de persistencia que guarde nuestros objetos de dominio, por ejemplo, una base de datos MySQL.
  • Construir un repositorio que abstraiga el acceso a la base de datos relacional y proporcione una interfaz sencilla para las consultas.
  • Construir un servicio de fábrica que genere los objetos y construya el árbol de agregados.
  • Proporcionar una infraestructura de servicios para introducir lógica no relevante para el dominio.
  • Hacer que la aplicación sea consciente del dominio.

Si echas un vistazo a los comentarios del artículo sobre la Programación Orientada a Aspectos (AOP), verás una interesante discusión sobre si un framework debe o no añadir su huella mediante anotaciones en los comentarios. El enfoque de FLOW3 se basa en cómo se implementa el Diseño Dirigido por Dominios. Echa un vistazo a este código:

Esta es una clase muy simple y no contiene mucha lógica empresarial, pero esto probablemente cambiará una vez que la aplicación crezca. FLOW3 está presente a través de algunas anotaciones en el código. Se define la clase como una entidad y se añaden algunas reglas de validación a aplicar (esto es opcional). Observa que hay una anotación llamada @ORM\Column(length=80). Esta es una información para la capa de persistencia y volveremos a ella en un momento.

FLOW3 utiliza aquí anotaciones para mantener el dominio limpio. Eres libre de usar la clase en cualquier otro lugar, ya que sigue siendo un objeto simple. Puedes optar por cambiar al framework symfony, que utiliza la misma capa de persistencia (Doctrine), por lo que el código casi funcionaría desde el principio. Al empujar la configuración del framework fuera del ámbito del intérprete de PHP, el dominio sigue siendo un simple objeto PHP. Puedes reutilizarlo incluso sin ningún tipo de marco.

Pero ahora que el framework conoce el objeto, puede calcular los requisitos de una tabla de la base de datos MySQL. Para almacenar instancias de la clase cliente, FLOW3 (y Doctrine como marco de persistencia) realizaría los siguientes pasos por ti:

  • Crear una tabla con el nombre client.
  • Añade una columna llamada nombre del tipo cadena con una longitud de 80 caracteres
  • Como no proporcionamos un identificador único (que es necesario para las entidades), FLOW3 tuvo la amabilidad de autogenerar uno para nosotros y añadir una celda de la tabla para ello.

La definición de las propiedades de los artículos de nuestro pedido puede tener el siguiente aspecto:

Observa que esto devuelve una Colección Doctrine, que es una especie de envoltura para un array, como los ArrayLists en Java. Esencialmente, esto significa que todos los elementos tienen que ser del tipo dado, en este caso Item. He optado por añadir una declaración de orden sobre cómo quiero que se organice la colección (por los precios de los artículos).

La contrapartida en la clase Item podría ser:

Es solo la punta del iceberg, pero debería darte una idea de cómo se pueden automatizar las cosas: Doctrine proporciona una poderosa estrategia sobre cómo mapear las asociaciones a las tablas donde almacena el objeto. Por ejemplo, dado que los artículos se traducirían en una relación de uno a varios (un pedido puede tener muchos artículos) en la base de datos, Doctrine añadiría silenciosamente una clave foránea para el pedido a la tabla de artículos. Si decides añadir un repositorio para el artículo (convirtiéndolo en un agregado), podrías acceder mágicamente a un método findByOrder(Order order). Esa es la razón por la que no nos preocupamos por las bases de datos o la persistencia durante la creación del dominio: es algo de lo que puede ocuparse un framework.

En caso de que seas nuevo en los marcos de persistencia, la forma de mapear objetos a una base de datos relacional se llama ORM (Object Relational Mapping). Tiene algunos inconvenientes de rendimiento, causados principalmente por los diferentes enfoques que tienen las bases de datos relacionales y el modelo de objetos. Hay largas discusiones al respecto. Sin embargo, en las aplicaciones CRUD modernas (no solo dirigidas por el dominio), ORM es el camino a seguir, principalmente por razones de mantenimiento y capacidad de expansión. Sin embargo, debes conocer tu ORM y entender bien cómo funciona. No pienses que ya no necesitas conocimientos sobre bases de datos.

Como habrás notado, tus objetos pueden ser bastante complejos y tener una larga línea de recorrido si tienen muchos hijos, que a su vez tienen muchos hijos propios.

Por lo tanto, una vez que el repositorio recupera los datos de una base de datos, hay que transformarlos en objetos de forma inteligente. Como ahora tenemos una capa de persistencia involucrada, la transformación es mucho más compleja que la simple instanciación de un objeto. Tenemos que gestionar la línea de travesía reduciendo al mínimo las llamadas pertinentes a la base de datos. No siempre se necesitan todos los hijos, por lo que se pueden recuperar a demanda.

Algunos objetos serán objetos de valor que necesitan ser creados solo una vez, lo que puede ahorrar mucha memoria. Por eso, cualquier capa de dominio necesita una fábrica inteligente que genere los objetos por ti. Por lo tanto, en los marcos modernos, el nuevo operador se considera de muy bajo nivel para las aplicaciones modernas. FLOW3 se esfuerza por ofrecer la posibilidad de instanciar objetos con la nueva palabra clave, pero la compilación en segundo plano cambia automáticamente la simple creación de objetos por una potente gestión de los mismos. Algunas de las características que debe tener tu gestor/fábrica de objetos, independientemente del framework que utilices, son

  • Gestión de entidades y objetos de valor
  • Una forma inteligente de proporcionar a los hijos objetos a la carta
  • Inyección de dependencias y servicios

Puede que hayas fruncido el ceño con la última frase. A lo largo de todo el artículo he hecho hincapié en utilizar objetos planos en el dominio, e incluso he violado el paradigma de "no te repitas" y lo he mencionado varias veces porque es muy importante para DDD. Y ahora te digo que tienes dependencias y servicios que deben formar parte de tu dominio...

Hay una triste verdad en el mundo real: no existe un dominio puro. Casi nunca se encontrará con un cliente que parta de cero, por lo que tendrás que satisfacer circunstancias como los sistemas heredados. Pueden tener una implementación horrible, pero la empresa no puede deshacerse de ella. Es posible que tenga que llamar a servicios y API y recuperar datos de varios terceros, y estos sistemas heredados influyen en el dominio empresarial.

Todo lo que hemos discutido hasta ahora es importante, pero la cuestión de cómo un marco de trabajo resuelve la dependencia de los servicios que no son del dominio es fundamental para un limpio Diseño dirigido por el dominio. Esta es la razón por la que el equipo de FLOW3 dedicó enormes esfuerzos a la implementación de la orientación a aspectos; es una forma de introducir servicios en el dominio sin tocar el código y sin violar la regla de los objetos simples. Hay otros enfoques, como trasladar las dependencias entre los servicios y el dominio al controlador, pero la programación orientada a aspectos es, con mucho, la forma más elegante que conozco. Me gustaría conocer tu opinión sobre este tema.

Un buen marco de trabajo puede darte mucho apoyo además de los puntos que he mencionado. Por ejemplo, FLOW3 maneja de forma transparente los objetos del dominio en la vista con su motor de plantillas notablemente genial llamado Fluid. Escribir plantillas de Fluid, una vez hecho el dominio, es tan relajante como un día de playa.


Resumen

Este artículo es solo una introducción al Diseño basado en el dominio. He presentado algunos de los conceptos básicos, pero tengo que admitir que puede ser difícil de entender solo con la teoría. Quiero animarte a que pruebes el Diseño basado en el dominio por ti mismo en un proyecto del mundo real. Experimentarás que los conceptos del dominio son muy intuitivos de usar.

Me han lanzado a DDD como un pez fuera del agua en un proyecto muy grande de extbase, sin mucho conocimiento previo de los conceptos (extbase es un marco de Diseño dirigido por el dominio para construir extensiones para el CMS Typo3 y está basado en FLOW3). Ha ampliado mi perspectiva sobre cómo pensar en el diseño de software, y espero que también amplíe la tuya.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.