Patrón de diseño de microservicios basado en subdominios (Enfoque DDD)

Los microservicios son piezas de software que deben hacer cumplir una función específica, preferiblemente cumpliendo con el principio de responsabilidad única (SRP); definir los límites no es tan sencillo y requiere de un detallado análisis. No existe un proceso exacto que siguiendo una lista de pasos nos ayude a diseñar microservicios coherentes.

El diseño de estas «piezas», es en mi opinión particular lo mas complejo de acometer en el proceso de desarrollo de una arquitectura basada en microservicios.

En nuestro libro, hablamos de varios patrones y principios para la descomposición de los microservicios.

Diseño guiado por el dominio (DDD)

El enfoque de descomposición por subdominios se basa en los principios de Domain Driven Design (DDD), descrito por Eric Evans en el libro Domain-Driven Design: Tackling Complexity in the Heart of Software del 2004. Para una comprensión más rápida de estos conceptos puede leerse en cambio el libro Domain-Driven Design Quickly del 2006, distribuido por InfoQ.

DDD propone un enfoque para alinear los expertos del dominio y desarrolladores en base a objetivos claros, con un lenguaje común y centrado en el negocio. Es una forma de desarrollar software empresarial para sistemas grandes y complejos. Se proponen tres ejes principales para guiar el desarrollo:

  • Centrarse en el dominio principal.
  • Estructurar el diseño en un modelo de dominio.
  • Realizar desarrollo iterativo para la mejora continua en estrecha colaboración entre los expertos del negocio y el equipo de desarrollo, usando un lenguaje común.

El diseño basado en dominio tienes dos partes principales:

Diseño estratégico: En esta etapa se define la estructura del sistema a gran escala, sin tener en cuenta ningún elemento técnico o tecnología, es un esbozo del negocio. En esta etapa se definen los contextos delimitados para los modelos de dominio.

Diseño táctico: Se definen un conjunto de modelos de diseño que pueden usarse para crear el modelo de dominio. Estos modelos incluyen entidades, agregados y servicios de dominio. Estos además ayudan a diseñar microservicios coherentes y con acoplamiento flexible, en el plano táctico se redefinen y afinan los contextos delimitados en la etapa estratégica. En una arquitectura de microservicios interesan los patrones de agregados y entidades. La aplicación de estos patrones ayuda a identificar los límites naturales de los microservicios de la aplicación. Como principio general, un microservicio no debe ser menor que un agregado ni mayor que un contexto delimitado.

Para tener claridad en los conceptos definiremos algunos términos relacionados, en la bibliografía antes referida se puede ampliar más sobre los conceptos de DDD.

  • Lenguaje ubicuo: Se refiere en DDD a un lenguaje común que debe adoptarse entre los programadores y usuarios.
  • Modelo de dominio: Es un modelo conceptual de todos los temas relacionados con un problema específico. En él se describen las distintas entidades, sus atributos, funciones y relaciones, además de las restricciones que rigen el dominio del problema.
  • Entidad: Una entidad es un objeto con una identidad única que persiste en el tiempo, una entidad puede abarcar varios contextos limitados. Por ejemplo, en una aplicación de gestión bibliotecaria, los libros y los clientes (lectores) son entidades.
  • Objetos de valor: Un objeto de valor no tiene identidad. Se define únicamente mediante los valores de sus atributos. Los objetos de valor también son inmutables. Para actualizar un objeto de valor, siempre hay que crear una nueva instancia que reemplace a la anterior. Los objetos de valor pueden tener métodos que encapsulen la lógica del dominio, pero esos métodos no deben afectar al estado del objeto. Ejemplos típicos de objetos de valor son los colores, las fechas, y los valores de divisa.
  • Agregados: Un agregado define un límite de relación alrededor de una o varias entidades. En la vida real en un negocio se definen relaciones complejas, por ejemplo: Un lector realiza un pedido, un pedido tiene un libro, un libro está en un casillero y así sucesivamente; de acuerdo a un negocio particular las relaciones entre entidades pueden ser complejas. El propósito de un agregado es modelar las invariantes transaccionales. A menudo, las aplicaciones tradicionales han usado las transacciones de base de datos para asegurar la coherencia. En una aplicación distribuida, sin embargo, eso no es factible. Una sola transacción puede abarcar varios almacenes de datos, puede ser de ejecución prolongada o implicar a servicios de terceros. En última instancia, depende de la aplicación, no del nivel de datos, el aplicar los valores invariables necesarios para el dominio. Eso es lo que los agregados se supone que modelan. Un agregado define un límite transaccional. Un agregado define un límite de coherencia alrededor de una o varias entidades. Una entidad exacta en un agregado es la raíz. La búsqueda se realiza con el identificador de la entidad raíz. Cualquier otra entidad en el agregado es secundaria de la raíz y se hace referencia a ella siguiendo punteros desde esta.
  • Servicios de aplicación y de dominio: En la terminología del diseño basado en dominios, un servicio es un objeto que implementa alguna lógica sin mantener ningún estado. Evans distingue entre servicios de dominio, que encapsulan la lógica del dominio, y servicios de aplicación, que proporcionan la funcionalidad técnica, como la autenticación del usuario o el envío de un mensaje SMS. Los servicios de dominio a menudo se utilizan para modelar el comportamiento que abarca varias entidades.
  • Contextos limitados: Un contexto limitado o delimitado (lo referiremos por igual) es simplemente el límite dentro de un dominio donde se aplica un submodelo de dominio en específico. Los elementos que forman parte de un contexto específico deben tener el mismo lenguaje contextual/ubicuo y cohesión en los datos.

Las entidades, objetos de valor, agregados y servicios de aplicación/dominio son patrones tácticos; es importante tener esto en cuenta pues lo usaremos más adelante.

A partir de las cuestiones conceptuales anteriores, vamos a seguir 4 pasos para llevar adelante la descomposición que dará lugar a nuestro diseño de microservicios.

Flujo de pasos para aplicar descomposición por subdominios
  • Paso 1: Se inicia por analizar el dominio de la empresa para conocer los requisitos funcionales de la aplicación. El resultado de este paso es una descripción informal del dominio, que puede perfilarse en un conjunto más formal de modelos de dominio.
  • Paso 2: Definir los contextos delimitados del dominio. Cada contexto delimitado contiene un modelo de dominio que representa un subdominio concreto de la aplicación.
  • Paso 3: Dentro de cada contexto delimitado, se aplican los modelos tácticos de diseño basado en dominios para definir las entidades, los agregados y los servicios de dominio.
  • Paso 4: Se usan los resultados de etapas anteriores para identificar los microservicios de la aplicación.

Para entender mejor, vamos a explicar los pasos anteriores a través de un caso de estudio.

Caso de estudio de descomposición por subdominios

Negocio: La empresa Prestaditos SA está iniciando un servicio de courier (entregas) basado en motocicletas. La empresa administra una flota de motocicletas. Las prestamistas se registran en el servicio y los usuarios pueden solicitar que un motociclista recoja los bienes para la entrega.

Cuando un cliente programa una recogida, se asigna una motocicleta y notifica al usuario con un tiempo de entrega estimado. Con la entrega en curso, el cliente puede realizar el seguimiento, con una fecha estimada que se actualiza constantemente.

Paso 1: Análisis del dominio

En el análisis del dominio vamos a partir del negocio descrito y con la ayuda de los expertos a definir un modelo abstracto, donde estén los elementos principales, sus relaciones e interacciones y los plasmaremos en un documento o pizarrón.

En la medida que se vaya profundizando en el análisis se podrán identificar subdominios, así como los elementos primarios (centro del negocio), auxiliares o de apoyo (que contribuyen al cumplimiento del objetivo principal), posibles integraciones con sistemas externos, etc.

Análisis del dominio

Luego del proceso de análisis en el que participan los expertos del negocio y el equipo de tecnología se determina un primer borrador del dominio para el proceso de negocio de Entregas de Prestaditos SA.

Del diagrama anterior se identifican:

  • Entregas: Es la parte fundamental del negocio, el núcleo, todo lo restante existe para garantizar que este proceso se desarrolle.
  • Gestión de motocicletas: Es una actividad clave, y se relaciona con el taller, encargado de velar por el estado técnico, la estimación de tiempo de las entregas en dependencia de la localización y la estimación de carga, que se realiza a partir de un análisis en base a datos históricos.
  • Gestión de cuentas de usuario: Es un subdominio que contribuye al proceso de entrega, al igual que facturación, devoluciones y atención al cliente.
  • Rentas: Es un proceso que permite a la empresa en base a las estimaciones de carga poner un grupo de motocicletas al alquiler de personas en fechas/horas particulares de baja demanda.

Paso 2: Definición de contextos limitados

El modelo de dominio ilustrado en la figura anterior, refiere a representaciones de la vida real, como son entregas, usuarios, facturas; pero esto no significa que la representación sea la misma en todas las partes del sistema.

En los contextos limitados la representación de las entidades se realiza de acorde al elemento de dominio, por ejemplo, en el caso en cuestión el subsistema de dominio que se encarga de la reparación de las motocicletas (taller), le interesa de las mismas: kilómetros recorridos, última fecha del cambio de aceite, última fecha de mantenimiento; en cambio al subsistema de entregas solo le interesaría saber si la motocicleta está disponible o no. En dependencia del contexto la entidad tiene una forma de representarse diferente.

Crear un modelo único para ser usado en Taller y Entregas sería innecesario además de complejo de mantener y evolucionar ante la llegada de nuevos requisitos de negocio.

Una de las claves de los contextos limitados es poder representar una entidad solo con los atributos que sean pertinentes en cada contexto.

Si aplicamos el concepto de contextos limitados podemos agrupar teniendo en cuenta si varias funciones comparten un único modelo de dominio.

Contextos delimitados

Hemos agrupado en contextos las funcionalidades acorde al dominio de negocio que comparten, sin embargo, los contextos no están aislados entre sí, necesariamente tienen que interactuar, las líneas discontinuas delimitan el negocio, las líneas completas muestran la relación en que dos contextos delimitados interactúan.

En el ejemplo el contexto de entregas depende de las cuentas de usuario para obtener la información de los clientes y de la gestión de motocicletas para programar una entrega en una motocicleta.

Las interacciones entre contextos se basan en contratos previamente definidos; en DDD existen dos patrones definidos llamados Open Host Service y Published Language, al extender la idea a los microservicios, los contratos de interacción (para casos de http/rest) se basarían en OpenAPI.

Paso 3: Definir entidades y agregados usando principios de diseño táctico

Los patrones tácticos se aplican dentro de un único contexto delimitado. En una arquitectura de microservicios, interesan especialmente los patrones de entidades y agregados.

Para seguir con el ejemplo vamos a trabajar en el contexto limitado de Entrega, aplicaremos los patrones tácticos que hemos definido en los conceptos.

El primer paso para aplicar los patrones es describir lo que se debe hacer dentro del contexto delimitado de Entrega.

  • Un cliente puede solicitar que se le realice una entrega de un producto ofrecido por una empresa previamente registrada en el servicio de Entregas.
  • La empresa que envía el paquete identifica el paquete con una etiqueta generada (un código QR).
  • Un repartidor recogerá en el origen el/los paquetes y los llevará hasta su destino.
  • Al solicitar una entrega la aplicación establece un tiempo de entrega estimado, basado en la posible ruta, clima y datos históricos.
  • Cuando el repartidor sale un cliente puede hacer seguimiento en tiempo real y obtener el tiempo de entrega recalculado.
  • Mientras el repartidor no salga con el paquete, el cliente puede cancelar la entrega.
  • La empresa que realiza el envío puede solicitar al cliente en la entrega una firma digital de verificación de recepción.
  • Los clientes pueden acceder a su historial de envíos.

A partir de las funcionalidades el equipo de desarrollo identifica las entidades siguientes:

  • Paquete
  • Entrega
  • Cuenta
  • Motocicleta
  • Notificación
  • Confirmación
  • QR

Las entidades Paquete, Entrega, Motocicleta y Cuenta son agregados que tienen como entidad central a Entrega. Las entidades Notificación y Confirmación son entidades secundarias a Entrega y QR es secundaria a Paquete.

Se identifican como objetos de valor el tiempo de espera estimado, la localización del paquete, el peso y dimensiones.

El agregado Entrega contiene las siguientes propiedades y referencias con otros agregados.

Estructura del agregado: Entrega

Se identifican como eventos del dominio:

  • Cuando la motocicleta está en camino para realizar la entrega se envían Notificaciones que definen la Posición y ruta recalculada.
  • La entidad Entrega al pasar por los diferentes estados va enviando eventos indicando la etapa en la que se encuentra (pendiente, recogido, en camino, cercano, entregado).

En el trabajo conjunto entre expertos y desarrolladores, estos últimos notaron la necesidad de introducir una funcionalidad más, referente a planificación de tiempo de entrega y estimación de tiempos por etapa. Al diseño entonces le agregaremos un par de servicios de dominio: un planificador, encargado de estimar tiempos y etapas; y un supervisor, que vela por el cumplimiento de los tiempos estimados en cada etapa. La aplicación de los patrones tácticos nos lleva al siguiente resultado.

Resultado de la aplicación de los patrones tácticos

Paso 4: Identificar los microservicios de la aplicación

Luego de aplicar los patrones tácticos sobre los contextos delimitados (en este ejemplo solo lo aplicamos sobre Entregas), entonces debemos proceder a la etapa de identificar los microservicios de la aplicación.

Un microservicio no debe ser mayor a un contexto limitado ni menor que un agregado. Por definición un contexto marca el límite de un submodelo de dominio en particular.

Un elemento importante para determinar microservicios son los agregados, estos son buenos candidatos pues deben cumplir muchas de las características deseables en los microservicios:

  • Debe tener alta cohesión funcional.
  • Debe definir un límite de persistencia.
  • Acoplamiento flexible.

Los servicios de dominio también son buenos candidatos para microservicios. Los servicios de dominio son operaciones sin estado a través de varios agregados.

Otros criterios para definir microservicios son los requisitos no funcionales del sistema: tamaño, crecimiento esperado, escalabilidad deseada, disponibilidad, tecnologías, tamaño del equipo de desarrollo, entre otros.

La Figura siguiente ilustra los microservicios resultantes de aplicar el proceso de descomposición. Los elementos enmarcados en color amarillo pertenecen a otros contextos y no representan microservicios concretos, ellos están sujetos a un análisis similar al realizado, pero no serán abordados en esta entrada por simplificación.

Microservicios para el contexto de Entregas

Las solicitudes de entregas se encolarán y el MS Planificador se encargará de organizar la Entrega del Paquete, los eventos de entrega (las etapas) se enviarán a una cola/tópico que los llevará hasta el Historial de Entregas. La misma cola/tópico podrá tener otros suscriptores (En el libro de microservicios, abordamos el patrón publicador/suscriptor) como el encargado de la notificación al usuario.

El diseño de los microservicios identificados puede someterse a validación a partir de los siguientes criterios:

  • Los microservicios deben cumplir con el principio de responsabilidad única.
  • El tamaño del microservicio es pequeño para ser gestionado/desarrollado por un equipo de pocas personas y de forma independiente.
  • Los servicios están desacoplados y la evolución de uno no afecta al otro.
  • Las llamadas servicio-servicio son pocas, o lo que es lo mismo, la necesidad de aplicar transaccionalidad distribuida es la mínima posible.

Con estos apuntes culminamos con la aplicación del patrón de descomposición basada en subdominios, es un proceso complejo, pero sin dudas muy útil.

Espero te sirva este artículo, perdón por la extensión, pero es tan complejo de explicar, como de aplicar.

Deja una respuesta

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