Capítulo 4. Redimensionar tus microservicios: Encontrar los límitesdel servicio

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Uno de los aspectos más difíciles de construir un sistema de microservicios con éxito es la identificación de los límites adecuados de los microservicios. Tiene sentido intuitivo que dividir una gran base de código en partes más pequeñas, más sencillas y más débilmente acopladas mejore la mantenibilidad, pero ¿cómo decidimos dónde y cómo dividir el código en partes para conseguir esas propiedades deseadas? ¿Qué reglas utilizamos para saber dónde acaba un servicio y empieza otro? Responder a estas preguntas fundamentales es todo un reto. Muchos equipos nuevos en microservicios tropiezan con ellas. Trazar los límites de los microservicios de forma incorrecta puede disminuir significativamente las ventajas de utilizar microservicios o, en algunos casos, incluso hacer descarrilar todo el esfuerzo. Por eso no es de extrañar que la pregunta más frecuente y apremiante que se hacen los profesionales de los microservicios sea: ¿cómo se puede trocear adecuadamente una aplicación más grande en una colección de microservicios?

En este capítulo, profundizamos en la metodología líder para el análisis, modelado y descomposición eficaces de grandes dominios (Domain-Driven Design), explicamos las ventajas de eficacia del uso de Event Storming para el análisis de dominios, y terminamos presentando la Fórmula de Dimensionamiento Universal, una guía única para el dimensionamiento eficaz de microservicios.

Por qué importan los límites, cuándo importan y cómo encontrarlos

Justo en el título del patrón arquitectónico , tenemos la palabra micro:¡la arquitectura que estamos diseñando es la de los "micro" servicios! Pero, ¿cómo de "micro" deben ser nuestros servicios? Evidentemente, no estamos midiendo la longitud física de algo y suponiendo que micro significa la millonésima parte de un metro (es decir, de la unidad base de longitud en el Sistema Internacional de Unidades). Entonces, ¿qué significa micro para nuestros fines? ¿Cómo se supone que vamos a dividir nuestro gran problema en servicios más pequeños para conseguir los beneficios prometidos de los servicios "micro"? ¿Quizá podríamos imprimir nuestro código fuente en papel, pegarlo todo y medir su longitud literal? O, bromas aparte, ¿deberíamos guiarnos por el número de líneas de nuestro código fuente, manteniendo ese número pequeño para asegurarnos de que cada uno de nuestros microservicios es también lo suficientemente pequeño? Pero, ¿qué es "suficiente"? ¿Quizá declaremos arbitrariamente que cada microservicio no debe tener más de 500 líneas de código? También podríamos trazar límites en los perímetros familiares y funcionales de nuestro código fuente y decir que cada capacidad granular representada por una función en el código fuente de nuestro sistema es un microservicio. De este modo podríamos construir toda nuestra aplicación con, digamos, funciones sin servidor, declarando que cada una de esas funciones es un microservicio. ¡Limpio y fácil! ¿Verdad? Puede que no.

En la práctica, se ha probado cada uno de estos enfoques simplistas y todos tienen importantes inconvenientes. Aunque las líneas de código fuente (SLOC) han gozado históricamente de cierto uso como medida del esfuerzo/complejidad, desde entonces se ha reconocido ampliamente que es una medida pobre para determinar la complejidad o el tamaño real de cualquier código y que puede manipularse fácilmente. Por tanto, aunque nuestro objetivo fuera crear servicios "pequeños" con la esperanza de mantenerlos sencillos, las líneas de código serían una medida pobre.

Trazar límites en los perímetros funcionales es aún más tentador. Y se ha vuelto aún más tentador con el aumento de popularidad de las funciones sin servidor, como las funciones Lambda de Amazon Web Services. Aprovechando la productividad y la amplia adopción de las Lambdas de AWS, muchos equipos se han apresurado a declarar esas funciones "microservicios". Hay una serie de problemas significativos si sigues este camino, los más importantes de los cuales son:

Trazar límites basándose en las necesidades técnicas es un antipatrón

Según Lewis y Fowler, los microservicios deben "organizarse en torno a las capacidades empresariales", no a las necesidades técnicas. Del mismo modo, Parnas, en un artículo de 1972, recomienda descomponer los sistemas basándose en la encapsulación modular de los cambios de diseño a lo largo del tiempo. Ninguno de estos enfoques se alinea necesariamente con los límites de las funciones sin servidor.

Demasiada granularidad, demasiado pronto

Un nivel explosivo de granularidad al principio del ciclo de vida del proyecto de microservicios puede introducir niveles aplastantes de complejidad que detendrán el esfuerzo de los microservicios en seco, incluso antes de que tenga la oportunidad de despegar y tener éxito.

En el Capítulo 1 expusimos el objetivo principal de una arquitectura de microservicios: se trata principalmente de minimizar los costes de coordinación, en un entorno complejo y multiequipo, para lograr la armonía entre velocidad y seguridad, a escala. Por tanto, los servicios deben diseñarse de forma que se minimicen las necesidades de coordinación entre los equipos que trabajan en los distintos microservicios. Sin embargo, si dividimos el código en funciones de un modo que no conduzca necesariamente a minimizar la coordinación, acabaremos teniendo microservicios de tamaño incorrecto. Suponer que cualquier forma de organizar el código en funciones sin servidor reducirá la coordinación es un error.

Antes dijimos que una razón importante para evitar un enfoque basado en el tamaño o alineado con las funciones al dividir una aplicación en microservicios es el peligro de una optimización prematura: tener demasiados servicios demasiado pequeños demasiado pronto en tu viaje por los microservicios. Los primeros en adoptar los microservicios, como Netflix, SoundCloud, Amazon y otros, acabaronteniendo muchos microservicios. Eso, sin embargo, no significa que estas empresas empezaran con cientos de microservicios muy granulares el primer día. Más bien, un gran número de microservicios es lo que optimizaron tras años de desarrollo, después de haber alcanzado la madurez operativa capaz de manejar el nivel de complejidad asociado a la alta granularidad de los microservicios.

Evita crear demasiados microservicios demasiado pronto

El dimensionamiento de los servicios en una arquitectura de microservicios es sin duda un viaje que debe desarrollarse con el tiempo. Una forma segura de sabotear todo el esfuerzo es intentar diseñar un sistema demasiado granular al principio de ese viaje.

Tanto si estás trabajando en un proyecto totalmente nuevo como si estás descomponiendo un monolito existente, el enfoque debe ser absolutamente empezar con sólo un puñado de servicios e ir aumentando lentamente el número de microservicios con el tiempo. Si esto lleva a que algunos de tus microservicios sean inicialmente más grandes que en su estado objetivo, no pasa nada. Puedes dividirlos más adelante.

Aunque empecemos con unos pocos microservicios, tomándonoslo con calma, necesitamos alguna metodología fiable para determinar cómo dimensionar los microservicios. A continuación, exploraremos las buenas prácticas utilizadas con éxito en el sector.

Diseño orientado al dominio y límites de los microservicios

Al principio de averiguar las buenas prácticas de diseño de microservicios , Sam Newman introdujo algunas reglas básicas fundamentales en su libro Building Microservices (O'Reilly). Sugirió que, al trazar los límites de los servicios, debemos esforzarnos por conseguir un diseño tal que los servicios resultantes sean:

Acoplamiento débil

Los servicios deben ser bastante ajenos e independientes entre sí, de modo que una modificación de código en uno de ellos no provoque efectos dominó en los demás. Probablemente también querremos limitar el número de tipos diferentes de llamadas en tiempo de ejecución de un servicio a otro, ya que, más allá del posible problema de rendimiento, las comunicaciones parlanchinas también pueden provocar un acoplamiento estrecho de los componentes. Tomando nuestro enfoque de "minimización de la coordinación", el beneficio del acoplamiento débil de los servicios es bastante obvio.

Alta cohesión

Las funciones presentes en un servicio deben estar muy relacionadas, mientras que las no relacionadas deben estar encapsuladas en otro lugar. De este modo, si necesitas cambiar una unidad lógica de funcionalidad, deberías poder cambiarla en un solo lugar, minimizando el tiempo necesario para liberar ese cambio (una métrica importante). En cambio, si tuviéramos que cambiar el código en varios servicios, tendríamos que liberar muchos servicios diferentes al mismo tiempo para realizar ese cambio. Eso requeriría niveles significativos de coordinación, especialmente si esos servicios son "propiedad" de varios equipos, y comprometería directamente nuestro objetivo de minimizar los costes de coordinación.

Alineados con las capacidades empresariales

Dado que la mayoría de las solicitudes de modificación o extensión de funcionalidades están impulsadas por necesidades empresariales, si nuestros límites están estrechamente alineados con los límites de las capacidades empresariales, se deduciría naturalmente que los requisitos de diseño primero y segundo, arriba mencionados, se satisfacen más fácilmente. En la época de las arquitecturas monolito, los ingenieros de software de intentaron a menudo estandarizar los "modelos de datos canónicos". Sin embargo, la práctica demostró, una y otra vez, que los modelos de datos detallados para modelar la realidad no duran mucho: cambian con bastante frecuencia y estandarizarlos conduce a frecuentes retrabajos. En su lugar, lo que es más duradero es un conjunto de capacidades empresariales que proporcionan tus subsistemas. Un módulo de contabilidad siempre podrá proporcionar el conjunto de capacidades deseado a tu sistema mayor, independientemente de cómo evolucione su funcionamiento interno con el tiempo.

Estos principios de diseño han demostrado ser muy útiles y han recibido una amplia adopción entre los profesionales de los microservicios. Sin embargo, son principios bastante ambiciosos y de alto nivel, y podría decirse que no proporcionan la orientación específica sobre el tamaño de los servicios que necesitan los profesionales del día a día. En busca de una metodología más práctica, muchos recurrieron al Diseño Orientado al Dominio.

La metodología de diseño de software conocida como Diseño Dirigido por Dominios (DDD) es muy anterior a la arquitectura de microservicios. Fue introducida por Eric Evans en 2003, en su libro seminal del mismo nombre, Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley). La premisa principal de la metodología es la afirmación de que, al analizar sistemas complejos, debemos evitar buscar un único modelo de dominio unificado que represente a todo el sistema. Más bien, como dice Evans en su libro

En los grandes proyectos coexisten varios modelos, y esto funciona bien en muchos casos. Diferentes modelos se aplican en diferentes contextos.

Una vez que Evans estableció que un sistema complejo es fundamentalmente una colección de múltiples modelos de dominio, dio el paso crítico adicional de introducir la noción de contexto limitado. Concretamente, afirmó que

Un Contexto Limitado define el ámbito de aplicabilidad de cada modelo.

Los contextos delimitados permiten a la implementación y ejecución en tiempo de ejecución de distintas partes de un sistema mayor sin corromper los modelos de dominio independientes presentes en ese sistema. Tras definir los contextos delimitados, Eric pasó a proporcionar también una fórmula útil para identificar los perímetros óptimos de un contexto delimitado estableciendo el concepto de Lenguaje Ubicuo.

Para entender el significado de Lenguaje Ubicuo, es importante observar que un modelo de dominio bien definido proporciona, ante todo, un vocabulario común de términos y nociones definidos, un lenguaje común para describir el dominio, que los expertos en la materia y los ingenieros desarrollan juntos en estrecha colaboración, equilibrando los requisitos empresariales y las consideraciones de implementación. Este lenguaje común, o vocabulario compartido, es lo que en DDD llamamos Lenguaje Ubicuo. La importancia de esta observación radica en reconocer que las mismas palabras pueden tener significados distintos en contextos delimitados diferentes. Un ejemplo clásico de esto se muestra en la Figura 4-1. El término cuenta tiene un significado muy distinto en los contextos de gestión de identidad y acceso, gestión de clientes y contabilidad financiera de un sistema de reservas online.

msur 0401
Figura 4-1. Según el dominio en el que aparezca, "cuenta" puede tener distintos significados

En efecto, para un contexto de gestión de identidades y accesos, una cuenta es un conjunto de credenciales utilizadas para la autenticación y la autorización. Para un contexto limitado a la gestión de clientes, una cuenta es un conjunto de atributos demográficos y de contacto, mientras que para un contexto de contabilidad financiera, probablemente sea información de pago y una lista de transacciones pasadas. Podemos ver que la misma palabra básica inglesa se utiliza con un significado significativamente distinto en contextos diferentes, y no pasa nada porque sólo tenemos que ponernos de acuerdo en el significado ubicuo de los términos (el Lenguaje Ubicuo) dentro del contexto acotado de un modelo de dominio específico. Según la DDD, observando los perímetros a través de los cuales los términos cambian de significado, podemos identificar los límites de los contextos.

En DDD, no todos los términos que vienen a la mente al hablar de un modelo de dominio entran en el Lenguaje Ubicuo correspondiente. Los conceptos de un contexto delimitado que son fundamentales para el objetivo principal del contexto forman parte del Lenguaje Ubicuo del equipo, todos los demás deben quedar fuera. Estos conceptos básicos pueden descubrirse a partir del conjunto de JTBD que crees para el contexto delimitado. Como ejemplo, veamos la Figura 4-2.

msur 0402
Figura 4-2. Utilizar la sintaxis de una Historia del Trabajo para identificar los términos clave de un Lenguaje Ubicuo

En este ejemplo, estamos utilizando el formato de Historia de un Trabajo que introdujimos en el Capítulo 3 y aplicándolo a un trabajo del contexto delimitado de identidad y control de acceso. Podemos ver que los sustantivos clave, resaltados en la Figura 4-2, se corresponden con los términos del Lenguaje Ubicuo relacionado. Recomendamos encarecidamente la técnica de utilizar sustantivos clave de Historias de Trabajos bien escritas en la identificación de los términos del vocabulario relevantes para tu Lenguaje Ubicuo.

Ahora que hemos discutido algunos conceptos clave de DDD, veamos también algo que puede ser muy útil para diseñar adecuadamente las interacciones de microservicios: el mapeo de contexto. Exploraremos aspectos clave del mapeo de contexto en la siguiente sección de .

Mapeo del contexto

En DDD, no intentamos describir un sistema complejo con un único modelo de dominio. Más bien, diseñamos múltiples modelos independientes que coexisten en el sistema. Estos subdominios suelen comunicarse entre sí mediante descripciones de interfaz publicadas. La representación de varios dominios en un sistema más amplio y la forma en que colaboran entre sí se denomina mapa de contexto. En consecuencia, el acto de identificar y describir dichas colaboraciones se conoce como mapeo de contexto, como se muestra en la Figura 4-3.

msur 0403
Figura 4-3. Mapeo del contexto

DDD identifica varios tipos principales de interacciones de colaboración al mapear contextos delimitados. El tipo más básico se conoce como núcleo compartido. Se produce cuando dos dominios se desarrollan en gran medida de forma independiente y, casi por accidente, acaban solapándose en algún subconjunto de los dominios del otro (véase la Figura 4-4). Dos partes pueden acordar colaborar en este núcleo compartido, que también puede incluir código y modelo de datos compartidos, así como la descripción del dominio.

msur 0404
Figura 4-4. Núcleo compartido

Aunque tentador en apariencia (después de todo, el deseo de colaboración es uno de los instintos más humanos), el núcleo compartido es un patrón problemático, especialmente cuando se utiliza para arquitecturas de microservicios. Por definición, un núcleo compartido requiere inmediatamente un alto grado de coordinación entre dos equipos independientes incluso para poner en marcha la relación, y sigue requiriendo coordinación para cualquier modificación posterior. Salpicar tu arquitectura de microservicios con núcleos compartidos introducirá muchos puntos de coordinación estrecha. En los casos en que tengas que utilizar un núcleo compartido en un ecosistema de microservicios, se aconseja que se designe a un equipo como propietario/curador principal, y que todos los demás sean colaboradores.

Alternativamente, dos contextos delimitados pueden entablar lo que DDD denomina un tipo de relación Corriente Arriba-Corriente Abajo. En este tipo de relación, el Upstream actúa como proveedor de alguna capacidad, y el Downstream es el consumidor de dicha capacidad. Dado que las definiciones de dominio y las implementaciones no se solapan, este tipo de relación está más débilmente acoplada que un núcleo compartido (véase la Figura 4-5).

msur 0405
Figura 4-5. Relación aguas arriba - aguas abajo

Según el tipo de coordinación y acoplamiento, un mapeo ascendente-descendente puede introducirse de varias formas:

Cliente-Proveedor

En un escenario cliente-proveedor, el Upstream (proveedor) proporciona funcionalidad al Downstream (cliente). Mientras la funcionalidad proporcionada sea valiosa, todo el mundo está contento; sin embargo, Upstream carga con la sobrecarga de la retrocompatibilidad. Cuando Upstream modifica su servicio, tiene que asegurarse de que no rompe nada para el cliente. Y lo que es más dramático, el Downstream (cliente) corre el riesgo de que el Upstream le rompa algo, intencionadamente o no, o de que ignore las necesidades futuras del cliente.

Conformista

Un caso extremo de los riesgos para una relación cliente-proveedor es la relación conformista. Es una variación de la relación ascendente-descendente, cuando el ascendente explícitamente no se preocupa o no puede preocuparse por las necesidades de su descendente. Es un tipo de relación de "úsalo a tu propio riesgo". La Corriente Ascendente proporciona alguna capacidad valiosa que la Corriente Descendente está interesada en utilizar, pero dado que la Corriente Ascendente no atenderá sus necesidades, la Corriente Descendente tiene que ajustarse constantemente a los cambios de la Corriente Ascendente.

Las relaciones conformistas suelen darse en las grandes organizaciones y sistemas cuando un subsistema mucho mayor es utilizado por otro más pequeño. Imagina que desarrollas una pequeña capacidad nueva dentro de un sistema de reservas de una compañía aérea y necesitas utilizar, por ejemplo, un sistema de pagos de la empresa. Es poco probable que un sistema empresarial tan grande dé la hora a una iniciativa nueva y pequeña, pero tampoco puedes reimplantar todo un sistema de pagos por tu cuenta. O bien tendrás que convertirte en un conformista, o bien otra solución viable puede ser separar los caminos. Esto último no siempre significa que vayas a implementar tú mismo una funcionalidad similar. Algo como un sistema de pagos es lo suficientemente complejo como para que ningún equipo pequeño lo implemente como trabajo secundario de otro objetivo, pero quizá puedas salir de los confines de tu empresa y utilizar en su lugar un proveedor de pagos disponible comercialmente, si tu empresa lo permite.

Además de convertirse en conformista o tomar caminos separados, la Corriente Descendente tiene algunas formas más sancionadas por la DDD de protegerse de la negligencia de su Corriente Ascendente: una capa anticorrupción y utilizar Corrientes Ascendentes que proporcionen interfaces de host abiertas.

Capa anticorrupción

En este escenario, el descendente crea una capa de traducción llamada capa anticorrupción (ACL) entre sus Lenguas Ubicuas y las del ascendente, para protegerse de futuros cambios de ruptura en la interfaz del ascendente. Crear una ACL es una medida de protección eficaz, y a veces necesaria, pero los equipos deben tener en cuenta que a largo plazo su mantenimiento puede resultar bastante caro para el flujo descendente (véase la Figura 4-6).

msur 0406
Figura 4-6. Capa anticorrupción
Servicio de acogida abierto

Cuando el Upstream sabe que múltiples Downstreams pueden estar utilizando sus capacidades, en lugar de intentar coordinar las necesidades de sus muchos consumidores actuales y futuros, debería definir y publicar una interfaz estándar, que todos los consumidores tendrán que adoptar. en DDD, tales Upstreams se conocen como servicios de host abierto. Al proporcionar un protocolo abierto y fácil de integrar para todas las partes autorizadas, y mantener la compatibilidad hacia atrás de dicho protocolo o proporcionar un versionado claro y seguro para él, el host abierto puede escalar sus operaciones sin mucho drama. Prácticamente todos los servicios públicos (API) utilizan este enfoque. Por ejemplo, cuando utilizas las API de un proveedor de una nube pública (AWS, Google, Azure, etc.), normalmente no te conocen ni te atienden específicamente, ya que tienen millones de clientes, pero son capaces de proporcionar y hacer evolucionar un servicio útil operando como un host abierto (ver Figura 4-7).

msur 0407
Figura 4-7. Abrir servicio host

Además de los tipos de relación entre dominios, los mapeos de contexto también pueden diferenciarse en función de los tipos de integración utilizados entre contextos delimitados.

Integraciones síncronas frente a asíncronas

Las interfaces de integración entre contextos delimitados pueden ser síncronas o asíncronas, como se muestra en la Figura 4-8. Ninguno de los patrones de integración asume fundamentalmente uno u otro estilo.

Los patrones habituales para las integraciones síncronas entre contextos son las API RESTful desplegadas a través de HTTP, los servicios gRPC que utilizan formatos binarios como protobuf y, más recientemente, los servicios que utilizan interfaces GraphQL.

En el lado asíncrono, los tipos de interacciones publicar-suscribir son los que marcan el camino. En este patrón de interacción, el Upstream puede generar eventos, y los servicios Downstream tienen trabajadores capaces e interesados en procesarlos, como se muestra en la Figura 4-8.

msur 0408
Figura 4-8. Integraciones síncronas y asíncronas

Las interacciones publicar-suscribir son más complejas de implementar y depurar, pero pueden proporcionar un nivel superior de escalabilidad, resistencia y flexibilidad, en el sentido de que: varios receptores, aunque se implementen con una pila tecnológica heterogénea, pueden suscribirse a los mismos eventos utilizando un enfoque y una implementación uniformes.

Para concluir el debate sobre los conceptos clave del Diseño Orientado al Dominio, debemos explorar el concepto de agregado. Lo discutiremos en la siguiente sección.

A DDD Agregado

En DDD, un agregado es una colección de objetos de dominio relacionados que los consumidores externos pueden ver como una sola unidad. Esos consumidores externos sólo hacen referencia a una única entidad del agregado, y esa entidad se conoce en DDD como raíz del agregado. Los agregados permiten a los dominios ocultar las complejidades internas de un dominio, y exponer sólo la información y las capacidades (interfaz) que son "interesantes" para un consumidor externo. Por ejemplo, en las correspondencias ascendente-descendente de las que hemos hablado antes, el descendente no tiene por qué, y normalmente no querrá, conocer todos y cada uno de los objetos de dominio del ascendente. En su lugar, verá el flujo ascendente como un agregado o una colección de agregados.

Veremos resurgir la noción de agregado en la próxima sección, cuando hablemos del Event Storming, una potente metodología que puede agilizar enormemente el proceso de análisis dirigido por dominios y convertirlo en un ejercicio mucho más rápido y divertido.

Introducción al Event Storming

El Diseño Dirigido por Dominios es una potente metodología de para analizar tanto la composición a nivel de todo el sistema (denominada "estratégica" en DDD) como la composición en profundidad (denominada "táctica") de tus grandes sistemas complejos. También hemos visto que el análisis DDD puede ayudarnos a identificar subcomponentes bastante autónomos, débilmente acoplados a través de contextos delimitados de sus respectivos dominios.

Es muy fácil llegar a la conclusión de que, para aprender a dimensionar correctamente los microservicios, sólo tenemos que ser realmente buenos en el análisis dirigido por dominios; si hacemos que toda nuestra empresa también aprenda y se enamore de él (porque el DDD es ciertamente un deporte de equipo), ¡estaremos en el camino del éxito!

En los primeros días de las arquitecturas de microservicios, el DDD fue tan universalmente proclamado como la única y verdadera forma de dimensionar los microservicios, que el auge de los microservicios dio un enorme impulso también a la práctica del DDD, o al menos más gente empezó a conocerlo y a hacer referencia a él. De repente, muchos ponentes hablaban de DDD en todo tipo de conferencias sobre software, y muchos equipos empezaron a afirmar que lo empleaban en su trabajo diario. Desgraciadamente, una mirada atenta descubrió fácilmente que la realidad era algo distinta y que el DDD se había convertido en una de esas cosas "de las que se habla mucho y se practican poco".

No nos malinterpretes: había gente que utilizaba el DDD mucho antes de los microservicios, y hay muchos que lo utilizan ahora también, pero hablando específicamente de su uso como herramienta para dimensionar microservicios, era más publicidad y vaporware que realidad.

Hay dos razones principales por las que hay más gente que habla de DDD que la que lo practica en serio: es complejo y es caro. Practicar DDD requiere bastantes conocimientos y experiencia. El libro original de Eric Evans sobre el tema tiene 520 páginas, y necesitarías leer al menos unos cuantos libros más para entenderlo realmente, por no hablar de adquirir cierta experiencia aplicándolo realmente en varios proyectos. Sencillamente, no había suficientes personas con los conocimientos y la experiencia necesarios, y la curva de aprendizaje era muy pronunciada.

Para agravar el problema, como ya hemos mencionado, el DDD es un deporte de equipo, y además requiere mucho tiempo. No basta con tener un puñado de tecnólogos versados en DDD; también tienes que convencer a tus equipos de negocio, producto, diseño, etc., para que participen en largas e intensas sesiones de diseño de dominios, por no hablar de explicarles al menos lo básico de lo que intentas conseguir. Ahora bien, en el gran esquema de las cosas, ¿merece la pena? Muy probablemente, sí: especialmente para sistemas grandes, arriesgados y caros, el DDD puede tener muchos beneficios. Sin embargo, si sólo quieres avanzar rápidamente y dimensionar algunos microservicios, y ya has cobrado tu capital político en el trabajo, vendiendo a todo el mundo la nueva cosa llamada microservicios, ¡buena suerte también pidiendo a un montón de gente ocupada que te dedique el tiempo suficiente para dimensionar bien tus servicios! Simplemente no ocurría: era demasiado caro y requería demasiado tiempo.

Y de repente, un tipo llamado Alberto Brandolini, que había invertido décadas en comprender mejores formas de colaboración entre equipos, ¡encontró un atajo! Propuso un proceso divertido, ligero y barato llamado Tormenta de Eventos, que se basa e inspira en gran medida en los conceptos de DDD, pero que puede ayudarte a encontrar contextos acotados en cuestión de horas, en lugar de semanas o meses. La introducción del Event Storming supuso un gran avance para la aplicabilidad barata de DDD, específicamente para el dimensionamiento de servicios. Por supuesto, no es un sustituto completo, y no te dará todas las ventajas del DDD formal (si no, sería mágico). Pero en lo que respecta al descubrimiento de contextos acotados, con una buena aproximación, ¡es realmente mágico!

El Event Storming es un ejercicio muy eficaz que ayuda a identificar contextos acotados de un dominio de forma ágil, divertida y eficiente, normalmente mucho más rápido que con el DDD completo más tradicional. Es un enfoque pragmático que reduce el coste del análisis DDD lo suficiente como para hacerlo viable en situaciones en las que el DDD no sería asequible de otro modo. Veamos cómo se ejecuta realmente esta "magia" del Event Storming.

El proceso de organización de eventos

La belleza del Event Storming reside en su ingeniosa simplicidad. En espacios físicos (preferiblemente, cuando sea posible), todo lo que necesitas para celebrar una sesión de Event Storming es una pared muy larga (cuanto más larga, mejor), un montón de suministros, sobre todo pegatinas y Sharpies, y de cuatro a cinco horas de tiempo de miembros bien representados de tu equipo. Para que una sesión de Event Storming tenga éxito, es fundamental que los participantes no sean sólo ingenieros. Una amplia participación de grupos como las partes interesadas en el producto, el diseño y el negocio marca una diferencia significativa. También puedes organizar sesiones virtuales de Event Storming utilizando herramientas de colaboración digital que pueden imitar el proceso físico descrito aquí.

El proceso de organizar sesiones físicas de Event Storming comienza con la compra de los suministros. Para facilitar las cosas, hemos creado una lista de la compra de Amazon que utilizamos para las sesiones de Event Storming (ver Figura 4-9). Se compone de:

  • Un gran número de pegatinas de distintos colores, sobre todo, naranja y azul, y luego varios colores más para distintos tipos de objetos. Necesitas muchos. (Las tiendas nunca tenían suficientes para mí, así que me acostumbré a comprar por Internet).

  • Un rollo de cinta blanca de artista de 1/2 pulgada.

  • Un rollo largo de papel (por ejemplo, papel de dibujo Mala de IKEA) que vamos a colgar en la pared utilizando la cinta de artista. Ve creando varios "carriles".

  • Al menos tantos Sharpies como participantes haya en la sesión. Todo el mundo debe tener el suyo.

  • ¿Hemos mencionado ya una pared larga y sin obstáculos a la que podamos pegar el rollo de papel?

msur 0409
Figura 4-9. Suministros necesarios para una sesión de Event Storming

Durante las sesiones de Event Storming, es muy valiosa la amplia participación, por ejemplo, de expertos en la materia, propietarios de productos y diseñadores de interacción. Las sesiones de Event Storming son lo suficientemente breves (sólo varias horas en lugar de análisis que requieren días o semanas) como para que, teniendo en cuenta el valor de sus resultados, la claridad que aportan a todos los grupos representados y el tiempo que ahorran a largo plazo, sean tiempo bien invertido para todos los participantes. Una sesión de Event Storming que se limite sólo a los ingenieros de software es casi inútil, ya que ocurre en una burbuja y no puede conducir a las conversaciones interfuncionales necesarias para obtener los resultados deseados.

Una vez que tenemos los suministros, la gran sala con una pared abierta con un rollo de papel que hemos pegado a ella, y todas las personas necesarias, nosotros (el facilitador) pedimos a todo el mundo que coja un puñado de notas adhesivas naranjas y un rotulador Sharpie personal. Luego les damos una tarea sencilla: escribir los acontecimientos clave del ámbito analizado como notas adhesivas naranjas (un acontecimiento por nota), expresados con un verbo en pasado, y colocar las notas a lo largo de una línea de tiempo en el papel pegado a la pared para crear un "carril" del tiempo, como se muestra enla Figura 4-10.

msur 0410
Figura 4-10. Una línea de tiempo de eventos con notas adhesivas

Los participantes no deben obsesionarse con la secuencia exacta de los acontecimientos, y en esta fase no debe haber coordinación de acontecimientos entre los participantes. Lo único que se les pide es que piensen individualmente en tantos acontecimientos como sea posible y coloquen a la izquierda los acontecimientos que crean que ocurren antes en el tiempo, y coloquen más a la derecha los acontecimientos posteriores. No les corresponde eliminar los duplicados. Al menos, todavía no. Esta fase de la tarea suele durar entre 30 minutos y una hora, dependiendo del tamaño del problema y del número de participantes. Normalmente, querrás que se generen al menos 100 notas adhesivas de eventos antes de poder considerarlo un éxito.

En la segunda fase del ejercicio, se pide al grupo que observe el conjunto de notas resultante en la pared y, con la ayuda del facilitador, empiece a ordenarlas en una línea de tiempo más coherente, identificando y eliminando los duplicados. Con tiempo suficiente, es muy útil que los participantes empiecen a crear una "línea argumental", recorriendo los acontecimientos en un orden que cree algo parecido a un "viaje del usuario". En esta fase, el equipo puede tener algunas preguntas o confusiones; no tratamos de resolver estas cuestiones, sino de capturarlas como "hotspots" -notas adhesivas de distintos colores (normalmente morado) que tienen las preguntas en ellas-. Los puntos calientes deberán responderse fuera de línea, en seguimientos. Esta fase también puede durar entre 30 y 60 minutos.

En la tercera etapa, creamos lo que en Event Storming se conoce como narrativa inversa. Básicamente, recorremos la línea de tiempo hacia atrás, desde el final hasta el principio, e identificamos comandos; cosas que causaron los acontecimientos. Utilizamos notas adhesivas de otro color (normalmente azul) para los comandos. En esta fase, tu guión gráfico puede parecerse a la Figura 4-11.

msur 0411
Figura 4-11. Introducir comandos en la línea de tiempo de Event Storming

Ten en cuenta que muchos comandos tendrán una relación de uno a uno con un acontecimiento. Te parecerá redundante, como si se dijera lo mismo en pasado que en presente. De hecho, si te fijas en la figura anterior, los dos primeros comandos son así. A menudo confunde a la gente nueva en Event Storming. ¡Ignóralo! No juzgamos durante el Event Storming, y aunque algunos comandos pueden ser 1:1 con los eventos, otros no lo serán. Por ejemplo, el comando "Enviar autorización de pago" desencadena un montón de eventos. Limítate a capturar lo que sabes/piensas que ocurre en la vida real y no te preocupes por hacer las cosas "bonitas" o "ordenadas". El mundo real que estás modelando también suele ser desordenado.

En la siguiente fase, reconocemos que los comandos no producen eventos directamente, sino que hay tipos especiales de entidades del dominio que aceptan comandos y producen eventos. En Event Storming, estas entidades se denominan agregados (sí, el nombre está inspirado en la noción similar de DDD). Lo que hacemos en esta etapa es reorganizar nuestros comandos y eventos, rompiendo la línea temporal cuando sea necesario, de forma que los comandos que van al mismo agregado se agrupen en torno a ese agregado y los eventos "disparados" por ese agregado también se desplacen a él. Puedes ver un ejemplo de esta etapa de Event Storming en la Figura 4-12.

msur 0412
Figura 4-12. Agregados en una línea de tiempo de Event Storming

Esta fase del ejercicio puede durar entre 15 y 25 minutos. Una vez que hayamos terminado, deberías descubrir que nuestro muro ahora se parece menos a una línea temporal de acontecimientos y más a un conjunto de acontecimientos y comandos agrupados en torno a agregados.

¿Adivina qué? Estos grupos son los contextos delimitados que buscábamos.

Lo único que nos queda es clasificar los distintos contextos por el nivel de su prioridad (similar a "raíz", "de apoyo" y "genérico" en DDD). Para ello, creamos una matriz de contextos/subdominios delimitados y los clasificamos según dos propiedades: dificultad y perímetro competitivo. En cada categoría, utilizamos tamaños de camiseta <S, M o L> para clasificarlos en consecuencia. Al final, la toma de decisiones sobre cuándo invertir esfuerzo se basa en las siguientes directrices:

  1. Gran ventaja competitiva/gran esfuerzo: son los contextos que hay que diseñar e implantar internamente y a los que hay que dedicar más tiempo.

  2. Pequeña ventaja/gran esfuerzo: ¡compra!

  3. Pequeña ventaja/pequeño esfuerzo: grandes asignaciones a los aprendices.

  4. Otras combinaciones son una moneda al aire y requieren un juicio de valor.

Nota

Esta última fase, el "análisis competitivo", no forma parte del proceso original de Event Storming de Brandolini, y fue propuesta por Greg Young para priorizar dominios en DDD en general. Nos parece un ejercicio útil y divertido cuando se hace con un nivel adecuado de humor.

Todo el proceso es muy interactivo, requiere la implicación de todos los participantes y suele acabar siendo divertido. Hará falta un facilitador experimentado para que las cosas vayan sobre ruedas, pero la buena noticia es que ser un buen facilitador no requiere el mismo esfuerzo que convertirse en un científico de cohetes (o en un experto en DDD). Tras leer este libro y facilitar algunas sesiones simuladas para practicar, ¡podrás convertirte fácilmente en un facilitador de Event Storming de primera clase!

Como facilitador, es una buena idea vigilar el tiempo y tener un plan para tu sesión. Para una sesión de cuatro horas, la distribución aproximada del tiempo sería la siguiente:

  • Fase 1 (~30 min): Descubrir los acontecimientos del dominio

  • Fase 2 (~45 min): Hacer cumplir el calendario

  • Fase 3 (~60 min): Narrativa inversa e identificación de comandos

  • Fase 4 (~30 min): Identificar agregados/contextos delimitados

  • Fase 5 (~15 min): Análisis competitivo

Y si observas que estos tiempos no suman 4 horas, ten en cuenta que querrás dar a la gente algunos descansos en medio, así como dejarte tiempo para preparar el espacio y proporcionar orientación al principio.

Presentamos la Fórmula Universal de Tallaje

Los contextos delimitados son un fantástico punto de partida para dimensionar los microservicios. Sin embargo, debemos ser cautos y no asumir que los límites de los microservicios son sinónimos de los contextos delimitados de DDD o Event Storming. No lo son. De hecho, no se puede suponer que los límites de los microservicios sean constantes en el tiempo. Evolucionan con el tiempo y tienden a seguir una granularidad creciente de los microservicios a medida que maduran las organizaciones y las aplicaciones de las que forman parte. Por ejemplo, Adrian Cockroft señaló que se trataba sin duda de una tendencia que se repetía y que habían observado durante su etapa en Netflix.

Nadie consigue que los límites de los microservicios sean perfectos desde el principio

En los casos de éxito en la adopción de microservicios, los equipos no empiezan con cientos de microservicios. Empiezan con un número mucho menor, estrechamente alineado con contextos delimitados. A medida que pasa el tiempo, los equipos dividen los microservicios cuando se encuentran con dependencias de coordinación que necesitan eliminar. Esto también significa que no se espera que los equipos establezcan los límites de los servicios "correctamente" desde el principio. En su lugar, los límites evolucionan con el tiempo, con una dirección general de mayor granularidad.

Cabe señalar que suele ser más fácil dividir un servicio que volver a unir varios, o trasladar una capacidad de un servicio a otro. Ésta es otra razón por la que recomendamos empezar con un diseño de granularidad gruesa y esperar a conocer mejor el dominio y tener suficiente complejidad antes de dividir y aumentar la granularidad del servicio.

Hemos descubierto que hay tres principios que funcionan bien juntos cuando se piensa en la granularidad de los microservicios. Llamamos a estos principios la Fórmula Universal de Dimensionamiento para microservicios.

La fórmula universal del tallaje

Para conseguir un dimensionamiento razonable de los microservicios, debes

  • Empieza con unos pocos microservicios, posiblemente utilizando contextos delimitados.

  • Sigue dividiendo a medida que crezcan tu aplicación y tus servicios, guiándote por las necesidades de evitar la coordinación.

  • Estar en la trayectoria correcta para disminuir la coordinación. Esto es mucho más importante que el estado actual de cómo se consigue "perfectamente" el dimensionamiento del servicio.

Resumen

En este capítulo abordamos de frente la cuestión crítica de cómo dimensionar adecuadamente los microservicios. Examinamos el Diseño Orientado al Dominio, una metodología popular para modelar la descomposición en sistemas complejos; explicamos el proceso de realizar un análisis de dominio altamente eficiente con la metodología Event Storming, e introdujimos la Fórmula de Dimensionamiento Universal, que ofrece una orientación única para dimensionar eficazmente los microservicios.

En los capítulos siguientes profundizaremos en la implementación, mostrando cómo gestionar los datos en un entorno de microservicios con componentes y débilmente acoplados. También te guiaremos a través de un ejemplo de implementación para nuestro proyecto de demostración: un sistema de reservas online.

Get Microservicios: En marcha now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.