Capítulo 1. ¿Qué son los microservicios? ¿Qué son los microservicios?

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

Los microservicios se han convertido en una opción de arquitectura cada vez más popular en la media década o más transcurrida desde que escribí la primera edición de este libro. No puedo atribuirme el mérito de la posterior explosión de popularidad, pero la prisa por hacer uso de las arquitecturas de microservicios significa que, si bien muchas de las ideas que plasmé anteriormente están ya probadas, han aparecido nuevas ideas al mismo tiempo que las prácticas anteriores han caído en desgracia. Así que, una vez más, ha llegado el momento de destilar la esencia de la arquitectura de microservicios, destacando los conceptos básicos que hacen que los microservicios funcionen.

Este libro en su conjunto está diseñado para ofrecer una amplia visión del impacto que tienen los microservicios en diversos aspectos de la entrega de software. Para empezar, este capítulo echará un vistazo a las ideas centrales que subyacen a los microservicios, el estado de la técnica que nos ha traído hasta aquí y algunas razones por las que estas arquitecturas se utilizan tan ampliamente.

Los microservicios de un vistazo

Los microservicios son servicios liberables de forma independiente que se modelan en torno a un dominio empresarial. Un servicio encapsula funcionalidad y la hace accesible a otros servicios a través de redes: construyes un sistema más complejo a partir de estos bloques de construcción. Un microservicio puede representar el inventario, otro la gestión de pedidos y otro el envío, pero juntos pueden constituir todo un sistema de comercio electrónico. Los microservicios son una opción de arquitectura centrada en ofrecerte muchas opciones para resolver los problemas a los que puedas enfrentarte.

Son un tipo de arquitectura orientada a servicios, aunque con opiniones sobre cómo deben trazarse los límites de los servicios, y en la que la capacidad de implementación independiente es clave. Son agnósticas a la tecnología, lo que constituye una de las ventajas que ofrecen.

Desde el exterior, un microservicio se trata como una caja negra. Aloja la funcionalidad empresarial en uno o varios puntos finales de red (por ejemplo, una cola o una API REST, como se muestra en la Figura 1-1), a través de los protocolos que resulten más apropiados. Los consumidores, ya sean otros microservicios u otro tipo de programas, acceden a esta funcionalidad a través de estos puntos finales en red. Los detalles internos de la implementación (como la tecnología en la que está escrito el servicio o el modo en que se almacenan los datos) están totalmente ocultos al mundo exterior. Esto significa que las arquitecturas de microservicios evitan el uso de bases de datos compartidas en la mayoría de las circunstancias; en su lugar, cada microservicio encapsula su propia base de datos cuando es necesario.

bms2 0101
Figura 1-1. Un microservicio que expone su funcionalidad a través de una API REST y un tema

Los microservicios adoptan el concepto de ocultación de información.1 Ocultar información significa esconder tanta información como sea posible dentro de un componente y exponer la menor posible a través de interfaces externas. Esto permite una separación clara entre lo que se puede cambiar fácilmente y lo que es más difícil de cambiar. La implementación que se oculta a las partes externas puede cambiarse libremente siempre que las interfaces en red que expone el microservicio no cambien de forma incompatible con el pasado. Los cambios dentro de los límites de un microservicio (como se muestra en la Figura 1-1) no deben afectar a un consumidor anterior, lo que permite la liberación independiente de la funcionalidad. Esto es esencial para permitir que nuestros microservicios se trabajen de forma aislada y se liberen bajo demanda. Tener unos límites de servicio claros y estables, que no cambien cuando cambie la implementación interna, da como resultado sistemas con un acoplamiento más laxo yuna cohesión más fuerte.

Aunque en estamos hablando de ocultar detalles de la implementación interna, sería negligente por mi parte no mencionar el patrón de Arquitectura Hexagonal, detallado por primera vez por Alistair Cockburn.2 Este patrón describe la importancia de mantener la implementación interna separada de sus interfaces externas, con la idea de que tal vez quieras interactuar con la misma funcionalidad a través de distintos tipos de interfaces. Dibujo mis microservicios como hexágonos en parte para diferenciarlos de los servicios "normales", pero también como homenaje a esta obra de arte anterior.

Conceptos clave de los microservicios

A pocas ideas básicas deben entenderse cuando se exploran los microservicios. Dado que a menudo se pasan por alto algunos aspectos, es vital explorar más a fondo estos conceptos para asegurarte de que comprendes qué es lo que hace que los microservicios funcionen.

Implementación independiente

La desplegabilidad independiente es la idea de que podemos hacer un cambio en un microservicio, desplegarlo y liberar ese cambio para nuestros usuarios, sin tener que desplegar ningún otro microservicio. Y lo que es más importante, no se trata sólo de que podamos hacerlo, sino de que así es como gestionas las implementaciones en tu sistema. Es una disciplina que adoptas como enfoque de despliegue por defecto. Se trata de una idea sencilla que, sin embargo, es compleja en su ejecución.

Consejo

Si sólo sacas una cosa de este libro y del concepto de microservicios en general, debería ser ésta: asegúrate de que adoptas el concepto de capacidad de implementación independiente de tus microservicios. Acostúmbrate a desplegar y liberar cambios en un único microservicio en producción sin tener que desplegar nada más. De ello se derivarán muchas cosas buenas.

Para que garantice una implementación independiente, tenemos que asegurarnos de que nuestros microservicios están débilmente acoplados: debemos poder cambiar un servicio sin tener que cambiar nada más. Esto significa que necesitamos contratos explícitos, bien definidos y estables entre los servicios. Algunas opciones de implementación lo dificultan: compartir bases de datos, por ejemplo, es especialmente problemático.

La capacidad de implementación independiente es, en sí misma, increíblemente valiosa. Pero para conseguir una capacidad de implementación independiente, hay muchas otras cosas que tienes que hacer bien y que, a su vez, tienen sus propias ventajas. Así que también puedes ver el enfoque en la capacidad de despliegue independiente como una función forzosa: al centrarte en esto como un resultado, conseguirás una serie de beneficios secundarios.

El deseo de servicios poco acoplados con interfaces estables guía nuestra forma de pensar sobre cómo encontrar los límites de nuestros microservicios en primer lugar.

Modelado en torno a un dominio empresarial

Las técnicas como el diseño dirigido por dominios pueden permitirte estructurar tu código para representar mejor el dominio del mundo real en el que opera el software.3 Con las arquitecturas de microservicios, utilizamos esta misma idea para definir los límites de nuestros servicios. Al modelar los servicios en torno a dominios empresariales, podemos facilitar el despliegue de nuevas funcionalidades y recombinar microservicios de distintas formas para ofrecer nuevas funcionalidades a nuestros usuarios.

Desplegar una función que requiere cambios en más de un microservicio es costoso. Tienes que coordinar el trabajo en cada servicio (y potencialmente en equipos distintos) y gestionar cuidadosamente el orden de implementación de las nuevas versiones de estos servicios. Eso requiere mucho más trabajo que hacer el mismo cambio dentro de un solo servicio (o dentro de un monolito, para el caso). Por lo tanto, queremos encontrar la forma de que los cambios entre servicios sean lo menos frecuentes posible.

A menudo veo arquitecturas por capas, como la tipificada por la arquitectura de tres capas de la Figura 1-2. Aquí, cada capa de la arquitectura representa un límite de servicio diferente, y cada límite de servicio se basa en una funcionalidad técnica relacionada. Si en este ejemplo tuviera que hacer un cambio sólo en la capa de presentación, sería bastante eficiente. Sin embargo, la experiencia ha demostrado que los cambios en la funcionalidad suelen abarcar varias capas en este tipo de arquitecturas, lo que requiere cambios en los niveles de presentación, aplicación y datos. Este problema se agrava si la arquitectura tiene aún más capas que el sencillo ejemplo de la Figura 1-2; a menudo, cada nivel se divide en más capas.

Al convertir nuestros servicios en rebanadas de funcionalidad empresarial de extremo a extremo, nos aseguramos de que nuestra arquitectura esté dispuesta para que los cambios en la funcionalidad empresarial sean lo más eficientes posible. Podría decirse que con los microservicios hemos tomado la decisión de dar prioridad a la alta cohesión de la funcionalidad empresarial sobre la alta cohesión de la funcionalidad técnica.

bms2 0102
Figura 1-2. Una arquitectura tradicional de tres niveles

Volveremos sobre la interacción del diseño basado en dominios y cómo interactúa con el diseño organizativo más adelante en este capítulo.

Ser dueño de su propio Estado

Una de las cosas con las que veo que la gente lo pasa peor es la idea de que los microservicios deben evitar el uso de bases de datos compartidas. Si un microservicio quiere acceder a los datos que posee otro microservicio, debe ir y pedírselos a ese segundo microservicio. Esto da a los microservicios la capacidad de decidir qué se comparte y qué se oculta, lo que nos permite separar claramente la funcionalidad que puede cambiar libremente (nuestra implementación interna) de la funcionalidad que queremos cambiar con poca frecuencia (el contrato externo que utilizan los consumidores).

Si en queremos hacer realidad la implementabilidad independiente, tenemos que asegurarnos de limitar los cambios retrocompatibles en nuestros microservicios. Si rompemos la compatibilidad con los consumidores upstream, les obligaremos a cambiar también. Tener una delimitación clara entre los detalles de la implementación interna y un contrato externo para un microservicio puede ayudar a reducir la necesidad de cambios incompatibles hacia atrás.

Ocultar el estado interno de en un microservicio es análogo a la práctica de la encapsulación en la programación orientada a objetos (OO). La encapsulación de datos en sistemas OO es un ejemplo de ocultación de información en acción.

Consejo

No compartas bases de datos a menos que sea realmente necesario. E incluso entonces haz todo lo posible por evitarlo. En mi opinión, compartir bases de datos es una de las peores cosas que puedes hacer si intentas conseguir una capacidad de implementación independiente.

Como se ha comentado en la sección anterior, queremos pensar en nuestros servicios como rebanadas de funcionalidad empresarial de extremo a extremo que, cuando proceda, encapsulen la interfaz de usuario (IU), la lógica empresarial y los datos. Esto se debe a que queremos reducir el esfuerzo necesario para cambiar la funcionalidad relacionada con el negocio. La encapsulación de datos y comportamiento de este modo nos proporciona una gran cohesión de la funcionalidad empresarial. Al ocultar la base de datos que respalda nuestro servicio, también nos aseguramos de reducir el acoplamiento. Volveremos sobre el acoplamiento y la cohesión en el Capítulo 2.

Talla

"¿Qué tamaño debe tener un microservicio?" es una de las preguntas más habituales que escucho. Teniendo en cuenta que la palabra "micro" está justo en el nombre, no es de extrañar. Sin embargo, cuando te adentras en lo que hace que los microservicios funcionen como tipo de arquitectura, el concepto de tamaño es en realidad uno de los aspectos menos interesantes.

¿Cómo se mide el tamaño? ¿Contando las líneas de código? Eso no tiene mucho sentido para mí. Algo que podría requerir 25 líneas de código en Java podría escribirse en 10 líneas de Clojure. Eso no quiere decir que Clojure sea mejor o peor que Java; simplemente, algunos lenguajes son más expresivos que otros.

James Lewis, director técnico de Thoughtworks, es conocido por decir que "un microservicio debe ser tan grande como mi cabeza". A primera vista, esto no parece muy útil. Después de todo, ¿qué tamaño tiene exactamente la cabeza de James? El razonamiento que subyace a esta afirmación es que un microservicio debe tener un tamaño que permita comprenderlo fácilmente. El reto, por supuesto, es que la capacidad de las distintas personas para entender algo no siempre es la misma, por lo que tendrás que juzgar tú mismo qué tamaño te conviene. Un equipo experimentado puede ser capaz de gestionar mejor una base de código mayor que otro equipo. Así que quizá sería mejor leer la cita de James aquí como "un microservicio debería ser tan grande como tu cabeza".

Creo que lo más cerca que estoy de que el "tamaño" tenga algún significado en términos de microservicios es algo que Chris Richardson, el autor de Microservice Patterns (Manning Publications), dijo una vez: el objetivo de los microservicios es tener "una interfaz lo más pequeña posible". Eso se alinea de nuevo con el concepto de ocultar información, pero representa un intento de encontrar un significado al término "microservicios" que no estaba ahíinicialmente. Cuando el término se utilizó por primera vez para definir estas arquitecturas, la atención, al menos al principio, no se centraba específicamente en el tamaño de las interfaces.

En última instancia, el concepto de tamaño es muy contextual. Habla con una persona que lleve 15 años trabajando en un sistema, y opinará que su sistema con 100.000 líneas de código es realmente fácil de entender. Pide la opinión de alguien totalmente nuevo en el proyecto, y sentirá que es demasiado grande. Del mismo modo, pregunta a una empresa que acaba de embarcarse en su transición a los microservicios y tiene quizás 10 o menos microservicios, y obtendrás una respuesta diferente a la que obtendrías de una empresa de tamaño similar para la que los microservicios han sido la norma durante muchos años y que ahora tiene cientos.

Insto a la gente a que no se preocupe por el tamaño. Cuando estás empezando, es mucho más importante que te centres en dos cosas clave. Primero, ¿cuántos microservicios puedes manejar? A medida que tengas más servicios, la complejidad de tu sistema aumentará, y necesitarás aprender nuevas habilidades (y quizás adoptar nueva tecnología) para hacer frente a esto. El paso a los microservicios introducirá nuevas fuentes de complejidad, con todos los retos que ello puede conllevar. Por eso soy un firme defensor de la migración incremental a una arquitectura de microservicios. En segundo lugar, ¿cómo definir los límites de los microservicios para sacarles el máximo partido, sin que todo se convierta en un lío horriblemente acoplado? Es mucho más importante centrarse en estos temas cuando inicias tu viaje.

Flexibilidad

Otra cita de James Lewis en es que "los microservicios te compran opciones". Lewis estaba siendo deliberado con sus palabras: te compran opciones. Tienen un coste, y debes decidir si el coste merece la pena por las opciones que quieres asumir. La flexibilidad resultante en varios ejes -organizativo, técnico, escala, robustez- puede ser increíblemente atractiva.

No sabemos lo que nos depara el futuro, así que nos gustaría tener una arquitectura que teóricamente pueda ayudarnos a resolver cualquier problema al que podamos enfrentarnos en el futuro. Encontrar el equilibrio entre mantener abiertas tus opciones y asumir el coste de arquitecturas como ésta puede ser un verdadero arte.

Piensa que adoptar microservicios es menos como accionar un interruptor, y más como girar un dial. A medida que subes el dial y tienes más microservicios, aumentas la flexibilidad. Pero es probable que también aumentes los puntos de dolor. Esta es otra razón por la que abogo firmemente por la adopción incremental de microservicios. Al subir el dial gradualmente, puedes evaluar mejor el impacto a medida que avanzas, y detenerte si es necesario.

Alineación de la arquitectura y la organización

MusicCorp, una empresa de comercio electrónico de que vende CD por Internet, utiliza la sencilla arquitectura de tres niveles mostrada anteriormente en la Figura 1-2. Hemos decidido trasladar MusicCorp al siglo XXI y, para ello, estamos evaluando la arquitectura actual del sistema. Tenemos una interfaz de usuario basada en web, una capa de lógica empresarial en forma de backend monolítico y almacenamiento de datos en una base de datos tradicional. Estas capas, como es habitual, pertenecen a equipos diferentes. Volveremos sobre las pruebas y tribulaciones de MusicCorp a lo largo del libro.

Queremos hacer una actualización sencilla de nuestra funcionalidad: queremos permitir que nuestros clientes especifiquen su género musical favorito. Esta actualización requiere que cambiemos la UI para mostrar la UI de elección de género, el servicio backend para permitir que el género aparezca en la UI y que se cambie el valor, y la base de datos para aceptar este cambio. Estos cambios deberán ser gestionados por cada equipo e implementados en el orden correcto, como se indica en la Figura 1-3.

bms2 0103
Figura 1-3. Realizar un cambio en los tres niveles es más complicado

Ahora bien, esta arquitectura no es mala. Toda arquitectura acaba optimizándose en torno a un conjunto de objetivos. La arquitectura de tres niveles es tan común en parte porque es universal: todo el mundo ha oído hablar de ella. Así que la tendencia a elegir una arquitectura común que puedes haber visto en otros lugares es a menudo una de las razones por las que seguimos viendo este patrón. Pero creo que la principal razón por la que vemos esta arquitectura una y otra vez es porque se basa en cómo organizamos nuestros equipos.

La ahora famosa ley de Conway dice lo siguiente:

Las organizaciones que diseñan sistemas... se ven obligadas a producir diseños que son copias de las estructuras de comunicación de estas organizaciones.

Melvin Conway, "¿Cómo inventan los comités?"

La arquitectura de tres niveles es un buen ejemplo de esta ley en acción. En el pasado, la forma principal en que las organizaciones de TI agrupaban a las personas era en función de su competencia básica: los administradores de bases de datos estaban en un equipo con otros administradores de bases de datos; los desarrolladores de Java estaban en un equipo con otros desarrolladores de Java; y los desarrolladores frontales (que hoy en día saben cosas exóticas como JavaScript y desarrollo de aplicaciones móviles nativas) estaban en otro equipo. Agrupamos a las personas en función de su competencia básica, de modo que creamos activos informáticos que pueden alinearse con esos equipos.

Eso explica por qué esta arquitectura es tan común. No es mala; simplemente está optimizada en torno a un conjunto de fuerzas: cómo agrupábamos tradicionalmente a las personas, en torno a la familiaridad. Pero las fuerzas han cambiado. Nuestras aspiraciones en torno a nuestro software han cambiado. Ahora agrupamos a las personas en equipos polivalentes para reducir los traspasos y los silos. Queremos distribuir el software mucho más rápido que antes. Eso nos está llevando a tomar decisiones diferentes sobre la forma en que organizamos nuestros equipos, de modo que los organizamos en función de la forma en que dividimos nuestros sistemas.

La mayoría de los cambios que se nos pide que hagamos en nuestro sistema están relacionados con cambios en la funcionalidad empresarial. Pero en la Figura 1-3, nuestra funcionalidad empresarial está, en efecto, repartida entre los tres niveles, lo que aumenta la probabilidad de que un cambio en la funcionalidad cruce capas. Se trata de una arquitectura que tiene una alta cohesión de tecnología relacionada, pero una baja cohesión de funcionalidad empresarial. Si queremos que sea más fácil hacer cambios, tenemos que cambiar la forma en que agrupamos el código, eligiendo la cohesión de la funcionalidad empresarial en lugar de la tecnología. Cada servicio puede acabar conteniendo o no una mezcla de estas tres capas, pero eso es una cuestión de implementación local del servicio.

Comparemos con una posible arquitectura alternativa, ilustrada en la Figura 1-4. En lugar de una arquitectura y organización en capas horizontales, desglosamos nuestra organización y arquitectura según líneas de negocio verticales. Aquí vemos un equipo dedicado que tiene la responsabilidad total de realizar cambios en aspectos del perfil del cliente, lo que garantiza que el alcance del cambio en este ejemplo se limita a un equipo.

Como implementación, esto podría lograrse mediante un único microservicio propiedad del equipo de perfiles que exponga una interfaz de usuario para permitir a los clientes actualizar su información, con el estado del cliente también almacenado dentro de este microservicio. La elección de un género favorito se asocia a un cliente determinado, por lo que este cambio es mucho más localizado. En la Figura 1-5, también mostramos la lista de géneros disponibles obtenida de un microservicio Catalog, algo que probablemente ya exista. También vemos un nuevo microservicio Recommendation que accede a la información de nuestro género favorito, algo que podría seguir fácilmente en una versión posterior.

bms2 0104
Figura 1-4. La interfaz de usuario está dividida y es propiedad de un equipo que también gestiona la funcionalidad del servidor que soporta la interfaz de usuario.
bms2 0105
Figura 1-5. Un microservicio dedicado a Customer puede facilitar mucho la grabación del género musical favorito de un cliente

En tal situación, nuestro microservicio Customer encapsula una pequeña porción de cada uno de los tres niveles: tiene un poco de interfaz de usuario, un poco de lógica de aplicación y un poco de almacenamiento de datos. Nuestro dominio empresarial se convierte en la fuerza principal que impulsa la arquitectura de nuestro sistema, lo que esperamos que facilite la realización de cambios, así como la alineación de nuestros equipos con las líneas de negocio de la organización.

A menudo, la interfaz de usuario de no la proporciona directamente el microservicio, pero incluso en ese caso, cabría esperar que la parte de la interfaz de usuario relacionada con esta funcionalidad siguiera siendo propiedad del Equipo de Perfil de Cliente, como indica la Figura 1-4. Este concepto de que un equipo sea propietario de una parte integral de la funcionalidad orientada al usuario está ganando adeptos. El libro Team Topologies4 introduce la idea de un equipo alineado con el flujo, que encarna este concepto:

Un equipo alineado con el flujo es un equipo alineado con un flujo de trabajo único y valioso... [E]l equipo está capacitado para crear y entregar valor al cliente o al usuario de la forma más rápida, segura e independiente posible, sin necesidad de recurrir a otros equipos para realizar partes del trabajo.

Los equipos mostrados en la Figura 1-4 serían equipos alineados con flujos, un concepto que exploraremos más a fondo en los Capítulos 14 y 15, incluyendo cómo funcionan en la práctica estos tipos de estructuras organizativas, y cómo se alinean con los microservicios.

El Monolito

Hemos hablado de microservicios en, pero la mayoría de las veces se habla de los microservicios como un enfoque arquitectónico alternativo a la arquitectura monolítica. Para distinguir más claramente la arquitectura de microservicios, y para ayudarte a comprender mejor si merece la pena considerar los microservicios, también debería hablar de lo que entiendo exactamente por monolitos.

Cuando hablo de monolitos a lo largo de este libro, me refiero principalmente a una unidad de implementación. Cuando toda la funcionalidad de un sistema debe desplegarse junta, lo considero un monolito. Podría decirse que hay múltiples arquitecturas que se ajustan a esta definición, pero voy a hablar de las que veo con más frecuencia: el monolito de un solo proceso, el monolito modular y el monolito distribuido.

El monolito monoproceso

El ejemplo más común que viene a la mente cuando se habla de monolitos es un sistema en el que todo el código se despliega como un único proceso, como en la Figura 1-6. Puedes tener varias instancias de este proceso por razones de robustez o escalabilidad, pero fundamentalmente todo el código está empaquetado en un único proceso. En realidad, estos sistemas de proceso único pueden ser sistemas distribuidos sencillos por derecho propio, porque casi siempre acaban leyendo datos de una base de datos o almacenándolos en ella, o presentando información a aplicaciones web o móviles.

bms2 0106
Figura 1-6. En un monolito de proceso único, todo el código se empaqueta en un único proceso

Aunque esto se ajusta a lo que la mayoría de la gente entiende por un monolito clásico, la mayoría de los sistemas que encuentro son algo más complejos que esto. Puede que tengas dos o más monolitos estrechamente acoplados entre sí, posiblemente con algún software de proveedor en la mezcla.

Una implementación monolítica clásica de un solo proceso puede tener sentido para muchas organizaciones. David Heinemeier Hansson, creador de Ruby on Rails, ha defendido eficazmente que una arquitectura de este tipo tiene sentido para las organizaciones más pequeñas.5 Sin embargo, incluso cuando la organización crece, el monolito puede crecer potencialmente con ella, lo que nos lleva al monolito modular.

El Monolito Modular

Como un subconjunto del monolito de proceso único, el monolito modular es una variación en la que el proceso único consta de módulos separados. Se puede trabajar en cada módulo de forma independiente, pero todos deben combinarse para su implementación, como se muestra en la Figura 1-7. El concepto de dividir el software en módulos no es nada nuevo; el software modular tiene sus raíces en el trabajo realizado en torno a la programación estructurada en la década de 1970, e incluso más atrás. Sin embargo, se trata de un enfoque que todavía no veo que suficientes organizaciones adopten adecuadamente.

bms2 0107
Figura 1-7. En un monolito modular, el código dentro del proceso se divide en módulos

Para muchas organizaciones, el monolito modular puede ser una opción excelente. Si los límites de los módulos están bien definidos, puede permitir un alto grado de trabajo en paralelo, al tiempo que evita los retos de la arquitectura de microservicios más distribuida al tener una topología de implementación mucho más sencilla. Shopify es un gran ejemplo de organización que ha utilizado esta técnica como alternativa a la descomposición de microservicios, y parece funcionar realmente bien para esa empresa.6

Uno de los retos de un monolito modular es que la base de datos tiende a carecer de la descomposición que encontramos en el nivel de código, lo que conlleva retos importantes si quieres desmontar el monolito en el futuro. He visto que algunos equipos intentan llevar más allá la idea del monolito modular descomponiendo la base de datos en la misma línea que los módulos, como se muestra en la Figura 1-8.

bms2 0108
Figura 1-8. Un monolito modular con una base de datos descompuesta

El monolito distribuido

Un sistema distribuido es aquel en el que el fallo de un ordenador que ni siquiera sabías que existía puede inutilizar tu propio ordenador.7

Leslie Lamport

Un monolito distribuido es un sistema que consta de múltiples servicios, pero por la razón que sea, todo el sistema debe desplegarse junto. Un monolito distribuido bien podría ajustarse a la definición de una SOA, pero con demasiada frecuencia, no cumple las promesas de la SOA. En mi experiencia, un monolito distribuido tiene todas las desventajas de un sistema distribuido, y las desventajas de un monolito de proceso único, sin tener suficientes ventajas de ninguno de los dos. Encontrarme con varios monolitos distribuidos en mi trabajo ha influido en gran medida en mi propio interés por la arquitectura de microservicios.

Los monolitos distribuidos suelen surgir en un entorno en el que no se prestó suficiente atención a conceptos como la ocultación de información y la cohesión de la funcionalidad empresarial. En su lugar, las arquitecturas altamente acopladas hacen que los cambios se propaguen a través de los límites de los servicios, y cambios aparentemente inocentes que parecen de alcance local rompen otras partes del sistema.

Monolitos y Contención de la Entrega

En, cada vez más personas trabajan en el mismo lugar y se estorban mutuamente: por ejemplo, distintos desarrolladores que quieren cambiar el mismo fragmento de código, distintos equipos que quieren lanzar la funcionalidad en distintos momentos (o retrasar las implementaciones) y confusión en torno a quién es dueño de qué y quién toma las decisiones. Multitud de estudios han demostrado los retos que plantean las líneas de propiedad confusas.8 Yo llamo a este problema contención en la entrega.

Tener un monolito no significa que te enfrentarás definitivamente a los retos de la contención en la entrega, como tampoco tener una arquitectura de microservicios significa que nunca te enfrentarás al problema. Pero una arquitectura de microservicios te proporciona unos límites más concretos en torno a los cuales se pueden trazar las líneas de propiedad en un sistema, lo que te da mucha más flexibilidad a la hora de reducir este problema.

Ventajas de los monolitos

Algunos monolitos de, como los monolitos monoproceso o modulares, también tienen toda una serie de ventajas. Su topología de implementación mucho más sencilla puede evitar muchos de los escollos asociados a los sistemas distribuidos. Esto puede dar lugar a flujos de trabajo de los desarrolladores mucho más sencillos, y el monitoreo, la resolución de problemas y actividades como las pruebas de extremo a extremo también pueden simplificarse enormemente.

Los monolitos también pueden simplificar la reutilización de código dentro del propio monolito. Si queremos reutilizar código dentro de un sistema distribuido, tenemos que decidir si queremos copiar código, dividir bibliotecas o introducir la funcionalidad compartida en un servicio. Con un monolito, nuestras opciones son mucho más sencillas, y a mucha gente le gusta esa simplicidad: todo el código está ahí; ¡sólo tienes que utilizarlo!

Por desgracia, la gente ha llegado a considerar el monolito como algo que hay que evitar, como algo inherentemente problemático. He conocido a muchas personas para las que el término monolito es sinónimo de legado. Esto es un problema. Una arquitectura monolítica es una elección, y una elección válida. Yo iría más lejos y diría que, en mi opinión, es la opción sensata por defecto como estilo arquitectónico. En otras palabras, busco una razón para convencerme de utilizar microservicios, en lugar de buscar una razón para no utilizarlos.

Si caemos en la trampa de socavar sistemáticamente el monolito como opción viable para entregar nuestro software, corremos el riesgo de no hacer lo correcto ni para nosotros mismos ni para los usuarios de nuestro software.

Tecnología habilitadora

Como he comentado antes en, no creo que necesites adoptar mucha tecnología nueva cuando empieces a utilizar microservicios. De hecho, eso puede ser contraproducente. En lugar de eso, a medida que vayas ampliando tu arquitectura de microservicios, debes buscar constantemente los problemas causados por tu sistema cada vez más distribuido, y luego la tecnología que pueda ayudarte.

Dicho esto, la tecnología ha desempeñado un papel importante en la adopción de los microservicios como concepto. Comprender las herramientas disponibles para ayudarte a sacar el máximo partido de esta arquitectura va a ser una parte clave para que cualquier implementación de microservicios sea un éxito. De hecho, me atrevería a decir que los microservicios requieren una comprensión de la tecnología de apoyo hasta tal punto que las distinciones anteriores entre arquitectura lógica y física pueden ser problemáticas: si estás implicado en ayudar a dar forma a una arquitectura de microservicios, necesitarás una amplia comprensión de estos dos mundos.

Exploraremos en detalle gran parte de esta tecnología en capítulos posteriores, pero antes de eso, presentemos brevemente algunas de las tecnologías facilitadoras que podrían ayudarte si decides hacer uso de los microservicios.

Agregación de registros y rastreo distribuido

Con el creciente número de procesos que gestionas, puede resultar difícil comprender cómo se comporta tu sistema en un entorno de producción. Esto, a su vez, puede dificultar mucho la resolución de problemas. Exploraremos estas ideas más a fondo en el Capítulo 10, pero como mínimo, recomiendo encarecidamente la implementación de un sistema de agregación de registros como requisito previo para adoptar una arquitectura de microservicios.

Consejo

Ten cuidado con adoptar demasiada tecnología nueva cuando empieces con los microservicios. Dicho esto, una herramienta de agregación de registros es tan esencial que deberías considerarla un requisito previo para adoptar microservicios.

Estos sistemas te permiten recopilar y agregar registros de todos tus servicios, proporcionándote un lugar central desde el que se pueden analizar los registros, e incluso convertirlos en parte de un mecanismo de alerta activa. Muchas opciones en este espacio se adaptan a numerosas situaciones. Soy un gran admirador de Humio por varias razones, pero los sencillos servicios de registro que proporcionan los principales proveedores de nubes públicas pueden ser lo suficientemente buenos para iniciarte.

Puedes hacer que estas herramientas de agregación de registros sean aún más útiles implementando IDs de correlación, en los que se utiliza un único ID para un conjunto relacionado de llamadas de servicio; por ejemplo, la cadena de llamadas que podrían activarse debido a la interacción del usuario. Registrando este ID como parte de cada entrada de registro, aislar los registros asociados a un determinado flujo de llamadas resulta mucho más fácil, lo que a su vez facilita enormemente la resolución de problemas.

A medida que aumenta la complejidad de tu sistema, resulta esencial considerar herramientas que te permitan explorar mejor lo que hace tu sistema, proporcionando la capacidad de analizar trazas de múltiples servicios, detectar cuellos de botella y hacer preguntas a tu sistema que no sabías que querrías hacer en primer lugar. Las herramientas de código abierto pueden proporcionar algunas de estas funciones. Un ejemplo es Jaeger, que se centra en el lado del trazado distribuido de la ecuación.

Pero los productos de como Lightstep y Honeycomb (mostrados en la Figura 1-9) llevan estas ideas más allá. Representan una nueva generación de herramientas que van más allá de los enfoques tradicionales de monitoreo, facilitando enormemente la exploración del estado de tu sistema en funcionamiento. Puede que ya dispongas de herramientas más convencionales, pero realmente deberías fijarte en las capacidades que ofrecen estos productos. Se han creado desde cero para resolver el tipo de problemas a los que se enfrentan los operadores de arquitecturas de microservicios.

bms2 0109
Figura 1-9. Una traza distribuida mostrada en Honeycomb, que te permite identificar dónde se emplea el tiempo en operaciones que pueden abarcar varios microservicios

Contenedores y Kubernetes

Lo ideal es que quiera ejecutar cada instancia de microservicio de forma aislada. Esto garantiza que los problemas de un microservicio no puedan afectar a otro microservicio, por ejemplo, consumiendo toda la CPU. La virtualización es una forma de crear entornos de ejecución aislados en el hardware existente, pero las técnicas de virtualización normales pueden resultar bastante pesadas si tenemos en cuenta el tamaño de nuestros microservicios. Los contenedores, en cambio, proporcionan una forma mucho más ligera de aprovisionar ejecución aislada para instancias de servicio, lo que se traduce en tiempos de arranque más rápidos para las nuevas instancias de contenedor, además de ser mucho más rentables para muchas arquitecturas.

Cuando empieces a jugar con contenedores, también te darás cuenta de que necesitas algo que te permita gestionar estos contenedores en muchas máquinas subyacentes. Las plataformas de orquestación de contenedores como Kubernetes hacen exactamente eso, permitiéndote distribuir instancias de contenedores de forma que proporcionen la robustez y el rendimiento que necesita tu servicio, al tiempo que te permiten hacer un uso eficiente de las máquinas subyacentes. En el Capítulo 8 exploraremos los conceptos de aislamiento operativo, contenedores y Kubernetes.

No sientas la necesidad de apresurarte a adoptar Kubernetes, ni siquiera los contenedores. Ofrecen absolutamente ventajas significativas sobre las técnicas de implementación más tradicionales, pero su adopción es difícil de justificar si sólo tienes unos pocos microservicios. Cuando la sobrecarga de gestionar la implementación empiece a convertirse en un quebradero de cabeza importante, empieza a considerar la posibilidad de contenerizar tu servicio y utilizar Kubernetes. Pero si acabas haciéndolo, haz todo lo posible para asegurarte de que otra persona ejecuta el clúster de Kubernetes por ti, quizás haciendo uso de un servicio gestionado en un proveedor de nube pública. Ejecutar tu propio clúster de Kubernetes puede suponer mucho trabajo.

Streaming

Aunque con los microservicios nos estamos alejando de las bases de datos monolíticas, seguimos necesitando encontrar formas de compartir datos entre microservicios. Esto está ocurriendo al mismo tiempo que las organizaciones desean alejarse de las operaciones de información por lotes y acercarse más a la información en tiempo real, lo que les permite reaccionar con mayor rapidez. Por tanto, los productos que permiten la transmisión y el procesamiento sencillos de lo que a menudo pueden ser grandes volúmenes de datos se han hecho populares entre quienes utilizan arquitecturas de microservicios.

Para mucha gente, Apache Kafka se ha convertido en la elección de facto para el streaming de datos en un entorno de microservicios, y por una buena razón. Capacidades como la permanencia de mensajes, la compactación y la capacidad de escalar para manejar grandes volúmenes de mensajes pueden ser increíblemente útiles. Kafka ha empezado a añadir capacidades de procesamiento de flujos en forma de KSQLDB, pero también puedes utilizarlo con soluciones dedicadas de procesamiento de flujos como Apache Flink. Debezium es una herramienta de código abierto desarrollada para ayudar a transmitir datos de fuentes de datos existentes a través de Kafka, ayudando a garantizar que las fuentes de datos tradicionales puedan formar parte de una arquitectura basada en flujos. En el Capítulo 4 veremos cómo la tecnología de streaming puede desempeñar un papel en la integración de microservicios.

Nube pública y sin servidor

Público los proveedores de la nube, o más concretamente los tres principales proveedores -Google Cloud, Microsoft Azure y Amazon Web Services (AWS)- ofrecen una enorme variedad de servicios gestionados y opciones de implementación para administrar tu aplicación. A medida que tu arquitectura de microservicios crezca, cada vez más trabajo pasará al espacio operativo. Los proveedores de nubes públicas ofrecen una gran cantidad de servicios gestionados, desde instancias de bases de datos gestionadas o clústeres de Kubernetes hasta intermediarios de mensajes o sistemas de archivos distribuidos. Al hacer uso de estos servicios gestionados, estás descargando gran parte de este trabajo a un tercero que posiblemente esté mejor capacitado para ocuparse de estas tareas.

De particular interés entre las ofertas de nube pública son los productos que se sientan bajo la bandera de sin servidor. Estos productos ocultan las máquinas subyacentes, permitiéndote trabajar a un mayor nivel de abstracción. Algunos ejemplos de productos sin servidor son los corredores de mensajes, las soluciones de almacenamiento y las bases de datos. Las plataformas de funciones como servicio (FaaS) son de especial interés porque proporcionan una buena abstracción en torno a la implementación del código. En lugar de preocuparte por cuántos servidores necesitas para ejecutar tu servicio, simplemente despliegas tu código y dejas que la plataforma subyacente se encargue de crear instancias de tu código bajo demanda. En el Capítulo 8 veremos con más detalle la tecnología sin servidor.

Ventajas de los microservicios

Las ventajas de los microservicios en son muchas y variadas. Muchas de estas ventajas pueden atribuirse a cualquier sistema distribuido. Los microservicios, sin embargo, tienden a lograr estas ventajas en mayor grado principalmente porque adoptan una postura más opinable en la forma de definir los límites del servicio. Combinando los conceptos de ocultación de información y diseño orientado al dominio con la potencia de los sistemas distribuidos, los microservicios pueden ayudar a obtener ventajas significativas sobre otras formas dearquitecturas distribuidas.

Heterogeneidad tecnológica

Con un sistema compuesto por múltiples microservicios que colaboran entre sí, podemos decidir utilizar diferentes tecnologías dentro de cada uno. Esto nos permite elegir la herramienta adecuada para cada trabajo, en lugar de tener que seleccionar un enfoque más estandarizado, de talla única, que a menudo acaba siendo el mínimo común denominador.

Si una parte de nuestro sistema necesita mejorar su rendimiento, podríamos decidir utilizar una pila tecnológica diferente que sea más capaz de alcanzar los niveles de rendimiento requeridos. También podríamos decidir que la forma en que almacenamos nuestros datos debe cambiar para distintas partes de nuestro sistema. Por ejemplo, para una red social, podríamos almacenar las interacciones de nuestros usuarios en una base de datos orientada a grafos, para reflejar la naturaleza altamente interconectada de un grafo social, pero quizás las publicaciones que hacen los usuarios podrían almacenarse en un almacén de datos orientado a documentos, dando lugar a una arquitectura heterogénea como la que se muestra en la Figura 1-10.

bms2 0110
Figura 1-10. Los microservicios pueden permitirte adoptar más fácilmente diferentes tecnologías

Con los microservicios, también podemos adoptar tecnologías más rápidamente y comprender cómo podrían ayudarnos los nuevos avances. Una de las mayores barreras para probar y adoptar una nueva tecnología son los riesgos asociados a ella. Con una aplicación monolítica, si quiero probar un nuevo lenguaje de programación, base de datos o marco de trabajo, cualquier cambio afectará a gran parte de mi sistema. Con un sistema formado por múltiples servicios, dispongo de múltiples lugares para probar una nueva tecnología. Puedo elegir un microservicio con quizás el menor riesgo y utilizar la tecnología allí, sabiendo que puedo limitar cualquier posible impacto negativo. Muchas organizaciones consideran que esta capacidad de absorber más rápidamente las nuevas tecnologías es una ventaja real.

Adoptar múltiples tecnologías no está exento de gastos, por supuesto. Algunas organizaciones optan por imponer algunas restricciones a la elección del lenguaje. Netflix y Twitter, por ejemplo, utilizan sobre todo la Máquina Virtual Java (JVM) como plataforma porque esas empresas conocen muy bien la fiabilidad y el rendimiento de ese sistema. También desarrollan bibliotecas y herramientas para la JVM que facilitan mucho el funcionamiento a escala, pero la dependencia de bibliotecas específicas de la JVM dificulta las cosas para los servicios o clientes no basados en Java. Pero ni Twitter ni Netflix utilizan una sola pila tecnológica para todos los trabajos.

El hecho de que la implementación interna de la tecnología esté oculta a los consumidores también puede facilitar la actualización de tecnologías. Toda tu arquitectura de microservicios podría basarse en Spring Boot, por ejemplo, pero podrías cambiar la versión de JVM o de framework sólo para un microservicio, lo que facilitaría la gestión del riesgo de actualizaciones.

Robustez

Un concepto clave de para mejorar la robustez de tu aplicación es el mamparo. Un componente de un sistema puede fallar, pero mientras ese fallo no se produzca en cascada, puedes aislar el problema, y el resto del sistema puede seguir funcionando. Los límites del servicio se convierten en tus mamparos evidentes. En un servicio monolítico, si el servicio falla, todo deja de funcionar. Con un sistema monolítico, podemos funcionar en varias máquinas para reducir las probabilidades de fallo, pero con los microservicios, podemos construir sistemas que gestionen el fallo total de algunos de los servicios constituyentes y degradar la funcionalidad en consecuencia.

Sin embargo, debemos tener cuidado. Para asegurarnos de que nuestros sistemas de microservicios pueden adoptar adecuadamente esta robustez mejorada, tenemos que comprender las nuevas fuentes de fallo con las que tienen que lidiar los sistemas distribuidos. Las redes pueden fallar y fallarán, al igual que las máquinas. Tenemos que saber cómo manejar esos fallos y el impacto (si lo hay) que tendrán en los usuarios finales de nuestro software. Ciertamente, he trabajado con equipos que han acabado con un sistema menos robusto tras su migración a microservicios debido a que no se tomaron estas preocupaciones lo suficientemente en serio.

Escalado

Con un servicio grande y monolítico, necesitamos escalarlo todo junto. Puede que una pequeña parte de nuestro sistema global tenga un rendimiento limitado, pero si ese comportamiento está encerrado en una aplicación monolítica gigante, tenemos que manejar el escalado de todo como una pieza. Con servicios más pequeños, podemos escalar sólo aquellos servicios que lo necesiten, permitiéndonos ejecutar otras partes del sistema en hardware más pequeño y menos potente, como se ilustra en la Figura 1-11.

bms2 0111
Figura 1-11. Puedes dirigir el escalado sólo a los microservicios que lo necesiten

Gilt, un minorista de moda online, adoptó los microservicios precisamente por esta razón. Tras empezar en 2007 con una aplicación Rails monolítica, en 2009 el sistema de Gilt era incapaz de soportar la carga que se le imponía. Al dividir las partes principales de su sistema, Gilt pudo hacer frente mejor a los picos de tráfico, y hoy cuenta con más de 450 microservicios, cada uno de los cuales se ejecuta en varias máquinas distintas.

Cuando adopta sistemas de aprovisionamiento bajo demanda como los que proporciona AWS, podemos incluso aplicar este escalado bajo demanda para aquellas piezas que lo necesiten. Esto nos permite controlar nuestros costes con mayor eficacia. No es frecuente que un enfoque arquitectónico pueda correlacionarse tan estrechamente con un ahorro de costes casi inmediato.

En última instancia, podemos escalar nuestras aplicaciones de múltiples maneras, y los microservicios pueden ser una parte eficaz de ello. Veremos el escalado de microservicios con más detalle en el Capítulo 13.

Facilidad de Implementación

Un cambio de una línea de en una aplicación monolítica de un millón de líneas requiere que se despliegue toda la aplicación para liberar el cambio. Eso podría ser una implementación de gran impacto y alto riesgo. En la práctica, este tipo de Implementaciones acaban produciéndose con poca frecuencia por un miedo comprensible. Por desgracia, esto significa que nuestros cambios siguen acumulándose entre versiones, hasta que la nueva versión de nuestra aplicación que entra en producción tiene una gran cantidad de cambios. Y cuanto mayor sea el delta entre versiones, ¡mayor será el riesgo de que nos equivoquemos!

Con los microservicios, podemos hacer un cambio en un solo servicio y desplegarlo independientemente del resto del sistema. Esto nos permite desplegar nuestro código más rápidamente. Si se produce un problema, se puede aislar rápidamente en un servicio individual, lo que facilita una rápida reversión. También significa que podemos hacer llegar nuestra nueva funcionalidad a los clientes más rápidamente. Esta es una de las principales razones por las que organizaciones como Amazon y Netflix utilizan estas arquitecturas: para asegurarse de que eliminan tantos impedimentos como sea posible para sacar el software a la calle.

Alineación organizativa

Muchos de nosotros hemos experimentado los problemas asociados a los grandes equipos y las grandes bases de código. Estos problemas pueden agravarse cuando el equipo está distribuido. También sabemos que los equipos más pequeños que trabajan en bases de código más pequeñas suelen ser más productivos.

Los microservicios nos permiten alinear mejor nuestra arquitectura con nuestra organización, ayudándonos a minimizar el número de personas que trabajan en cualquier base de código para alcanzar el punto óptimo de tamaño de equipo y productividad. Los microservicios también nos permiten cambiar la propiedad de los servicios a medida que cambia la organización, lo que nos permite mantener la alineación entre arquitectura y organización en el futuro.

Composibilidad

Una de las promesas clave de los sistemas distribuidos y las arquitecturas orientadas a servicios es que abrimos oportunidades para la reutilización de la funcionalidad. Con los microservicios, permitimos que nuestra funcionalidad se consuma de distintas formas para distintos fines. Esto puede ser especialmente importante cuando pensamos en cómo utilizan nuestro software los consumidores.

Se acabó la época en la que podíamos pensar estrictamente en nuestro sitio web de escritorio o en nuestra aplicación móvil. Ahora tenemos que pensar en las innumerables formas en que podríamos querer entrelazar capacidades para la web, la aplicación nativa, la web móvil, la aplicación para tableta o el dispositivo wearable. A medida que las organizaciones pasan de pensar en términos de canales limitados a adoptar conceptos más holísticos de la participación del cliente, necesitamos arquitecturas que puedan seguir el ritmo.

Con los microservicios, piensa que estamos abriendo costuras en nuestro sistema que son abordables por terceros. A medida que cambian las circunstancias, podemos construir aplicaciones de diferentes maneras. Con una aplicación monolítica, a menudo tengo una costura de grano grueso que se puede utilizar desde el exterior. Si quiero romperla para conseguir algo más útil, ¡necesitaré un martillo!

Puntos débiles de los microservicios

Las arquitecturas de microservicios aportan multitud de ventajas, como ya hemos visto. Pero también conllevan una gran complejidad. Si estás pensando en adoptar una arquitectura de microservicios, es importante que seas capaz de comparar lo bueno con lo malo. En realidad, la mayoría de los puntos de los microservicios pueden achacarse a los sistemas distribuidos y , por tanto, serían igual de evidentes en un monolito distribuido que en una arquitectura de microservicios.

Trataremos muchas de estas cuestiones en profundidad a lo largo del resto del libro; de hecho, diría que la mayor parte de este libro trata del dolor, el sufrimiento y el horror de poseer una arquitectura de microservicios.

Experiencia como desarrollador

Como tienes cada vez más servicios, la experiencia del desarrollador puede empezar a resentirse. Los tiempos de ejecución que consumen más recursos, como la JVM, pueden limitar el número de microservicios que pueden ejecutarse en una sola máquina de desarrollador. Probablemente podría ejecutar cuatro o cinco microservicios basados en JVM como procesos separados en mi portátil, pero ¿podría ejecutar 10 ó 20? Lo más probable es que no. Incluso con tiempos de ejecución menos exigentes, hay un límite en el número de cosas que puedes ejecutar localmente, lo que inevitablemente iniciará conversaciones sobre qué hacer cuando no puedas ejecutar todo el sistema en una sola máquina. Esto puede complicarse aún más si utilizas servicios en la nube que no puedes ejecutar localmente.

Las soluciones extremas pueden implicar "desarrollar en la nube", donde los desarrolladores dejan de poder desarrollar localmente. No soy partidario de esto, porque los ciclos de retroalimentación pueden resentirse mucho. En su lugar, creo que limitar el alcance de las partes del sistema en las que debe trabajar un desarrollador es un enfoque mucho más sencillo. Sin embargo, esto podría ser problemático si quieres adoptar más un modelo de "propiedad colectiva" en el que se espera que cualquier desarrollador trabaje en cualquier parte del sistema.

Sobrecarga tecnológica

El peso de la nueva tecnología que ha surgido para permitir la adopción de arquitecturas de microservicios puede ser abrumador. Seré sincero y diré que mucha de esta tecnología se ha rebautizado como "apta para microservicios", pero algunos avances han ayudado legítimamente a abordar la complejidad de este tipo de arquitecturas. Sin embargo, existe el peligro de que esta abundancia de nuevos juguetes pueda conducir a una forma de fetichismo tecnológico. He visto a muchas empresas que adoptaban la arquitectura de microservicios y decidían que también era el mejor momento para introducir enormes conjuntos de tecnología nueva y a menudo ajena.

Los microservicios pueden darte la opción de que cada microservicio se escriba en un lenguaje de programación diferente, se ejecute en un tiempo de ejecución distinto o utilice una base de datos diferente, pero se trata de opciones, no de requisitos. Tienes que sopesar cuidadosamente la amplitud y complejidad de la tecnología que utilizas frente a los costes que puede conllevar un conjunto diverso de tecnologías.

Cuando empiezas a adoptar microservicios, algunos retos fundamentales son ineludibles: tendrás que dedicar mucho tiempo a comprender cuestiones relacionadas con la coherencia de los datos, la latencia, el modelado de servicios y similares. Si intentas comprender cómo estas ideas cambian tu forma de pensar sobre el desarrollo de software al mismo tiempo que adoptas una enorme cantidad de tecnología nueva, lo tendrás difícil. También merece la pena señalar que el ancho de banda que te ocupa intentar comprender toda esta nueva tecnología reducirá el tiempo del que dispones para enviar realmente funciones a tus usuarios.

A medida que aumentes (gradualmente) la complejidad de tu arquitectura de microservicios, procura introducir nueva tecnología cuando la necesites. ¡No necesitas un clúster de Kubernetes cuando tienes tres servicios! Además de asegurarte de que no te sobrecargas con la complejidad de estas nuevas herramientas, este aumento gradual tiene la ventaja añadida de permitirte adquirir nuevas y mejores formas de hacer las cosas que, sin duda, surgirán con el tiempo.

Coste

Es muy probable que, al menos a corto plazo, veas un aumento de los costes debido a varios factores. En primer lugar, es probable que necesites ejecutar más cosas: más procesos, más ordenadores, más red, más almacenamiento y más software de apoyo (que conllevará el pago de licencias adicionales).

En segundo lugar, cualquier cambio que introduzcas en un equipo o en una organización te ralentizará a corto plazo. Lleva tiempo aprender nuevas ideas y averiguar cómo utilizarlas eficazmente. Mientras esto ocurre, otras actividades se verán afectadas. Esto se traducirá en una ralentización directa de la entrega de nuevas funcionalidades o en la necesidad de incorporar más personal para compensar este coste.

Según mi experiencia, los microservicios son una mala elección para una organización preocupada principalmente por reducir costes, ya que una mentalidad de recorte de gastos -en la que las TI se consideran un centro de costes en lugar de un centro de beneficios- será constantemente un lastre para sacar el máximo partido de esta arquitectura. Por otro lado, los microservicios pueden ayudarte a ganar más dinero si puedes utilizar estas arquitecturas para llegar a más clientes o desarrollar más funcionalidades en paralelo. Entonces, ¿son los microservicios una forma de obtener más beneficios? Quizás sí. ¿Son los microservicios una forma de reducir costes? No tanto.

Informar

Con un sistema monolítico, suele tener una base de datos monolítica. Esto significa que los interesados que quieran analizar todos los datos juntos, lo que a menudo implica grandes operaciones de unión entre datos, tienen un esquema listo contra el que ejecutar sus informes. Pueden ejecutarlos directamente contra la base de datos monolítica, quizás contra una réplica de lectura, como se muestra en la Figura 1-12.

bms2 0112
Figura 1-12. Elaboración de informes directamente sobre la base de datos de un monolito

Con una arquitectura de microservicios, hemos roto este esquema monolítico. Eso no significa que haya desaparecido la necesidad de elaborar informes sobre todos nuestros datos; simplemente lo hemos hecho mucho más difícil, porque ahora nuestros datos están dispersos en múltiples esquemas lógicamente aislados.

Los enfoques más modernos de la elaboración de informes, como el uso de streaming para permitir la elaboración de informes en tiempo real sobre grandes volúmenes de datos, pueden funcionar bien con una arquitectura de microservicios, pero normalmente requieren la adopción de nuevas ideas y tecnología asociada. Alternativamente, puede que simplemente necesites publicar los datos de tus microservicios en bases de datos centrales de informes (o quizás lagos de datos menos estructurados) para permitir casos de uso de informes.

Monitoreo y resolución de problemas

Con una aplicación monolítica estándar, podemos tener un enfoque bastante simplista del monitoreo. Tenemos un pequeño número de máquinas de las que preocuparnos, y el modo de fallo de la aplicación es en cierto modo binario: la aplicación suele estar toda arriba o toda abajo. Con una arquitectura de microservicios, ¿comprendemos el impacto si se cae una sola instancia de un servicio?

Con un sistema monolítico, si nuestra CPU está atascada al 100% durante mucho tiempo, sabemos que es un gran problema. Con una arquitectura de microservicios con decenas o cientos de procesos, ¿podemos decir lo mismo? ¿Necesitamos despertar a alguien a las 3 de la mañana cuando un solo proceso está atascado al 100% de la CPU?

Por suerte, hay toda una serie de ideas en este espacio que pueden ayudar. Si quieres explorar este concepto con más detalle, te recomiendo Distributed Systems Observability, de Cindy Sridharan (O'Reilly), como excelente punto de partida, aunque también echaremos nuestro propio vistazo al monitoreo y la observabilidad en el Capítulo 10.

Seguridad

Con, un sistema monolítico de proceso único, gran parte de nuestra información fluía dentro de ese proceso. Ahora, más información fluye por las redes entre nuestros servicios. Esto puede hacer que nuestros datos sean más vulnerables a ser observados en tránsito y también a ser potencialmente manipulados como parte de ataques man-in-the-middle. Esto significa que puede que tengas que dedicar más atención a proteger los datos en tránsito y a asegurarte de que los puntos finales de tus microservicios están protegidos para que sólo las partes autorizadas puedan hacer uso de ellos. El Capítulo 11 está dedicado íntegramente a examinar los retos en este espacio.

Prueba

Con cualquier tipo de prueba funcional automatizada, tienes un delicado acto de equilibrio. Cuanta más funcionalidad ejecute una prueba -es decir, cuanto mayor sea su alcance-, más confianza tendrás en tu aplicación. Por otro lado, cuanto mayor sea el alcance de la prueba, más difícil será configurar los datos de prueba y los accesorios de apoyo, más tiempo puede tardar en ejecutarse la prueba y más difícil puede ser averiguar qué se ha roto cuando falla. En el Capítulo 9 compartiré una serie de técnicas para hacer que las pruebas funcionen en este entorno más difícil.

Las pruebas de extremo a extremo para cualquier tipo de sistema se encuentran en el extremo de la escala en cuanto a la funcionalidad que cubren, y estamos acostumbrados a que sean más problemáticas de escribir y mantener que las pruebas unitarias de menor alcance. Sin embargo, a menudo merece la pena, porque queremos la confianza que da que una prueba de extremo a extremo utilice nuestros sistemas del mismo modo que lo haría un usuario.

Pero con una arquitectura de microservicios, el alcance de nuestras pruebas de extremo a extremo se hace muy grande. Ahora tendríamos que ejecutar pruebas en múltiples procesos, todos los cuales deben desplegarse y configurarse adecuadamente para los escenarios de prueba. También tenemos que estar preparados para los falsos negativos que se producen cuando los problemas del entorno, como la muerte de instancias de servicio o los tiempos de espera de la red por implementaciones fallidas, hacen que nuestras pruebas fallen.

Estas fuerzas significan que, a medida que crezca tu arquitectura de microservicios, obtendrás un rendimiento decreciente de la inversión en lo que se refiere a las pruebas de extremo a extremo. Las pruebas costarán más, pero no conseguirán darte el mismo nivel de confianza que en el pasado. Esto te impulsará hacia nuevas formas de pruebas, como las pruebas basadas en contratos o las pruebas en producción, así como la exploración de técnicas de entrega progresiva, como las ejecuciones paralelas o los lanzamientos canarios, que veremos en el Capítulo 8.

Latencia

Con una arquitectura de microservicios, el procesamiento que antes podía hacerse localmente en un procesador puede acabar ahora dividiéndose en múltiples microservicios separados. La información que antes fluía dentro de un único proceso ahora tiene que serializarse, transmitirse y deserializarse a través de redes que puede que estés ejercitando más que nunca. Todo esto puede provocar un empeoramiento de la latencia de tusistema.

Aunque puede ser difícil medir el impacto exacto en la latencia de las operaciones en la fase de diseño o codificación, ésta es otra razón por la que es importante emprender cualquier migración de microservicios de forma incremental. Haz un pequeño cambio y luego mide el impacto. Esto supone que tienes alguna forma de medir la latencia de extremo a extremo de las operaciones que te interesan: las herramientas de seguimiento distribuido comoJaeger pueden ayudar en este caso. Pero también tienes que saber qué latencia esaceptable para estas operaciones. A veces, hacer que una operación sea más lenta es perfectamente aceptable, ¡siempre que siga siendo lo suficientemente rápida!

Coherencia de los datos

El cambio de de un sistema monolítico, en el que los datos se almacenan y gestionan en una única base de datos, a un sistema mucho más distribuido, en el que múltiples procesos gestionan el estado en diferentes bases de datos, provoca posibles retos con respecto a la coherencia de los datos. Mientras que en el pasado podrías haber confiado en las transacciones de la base de datos para gestionar los cambios de estado, tendrás que comprender que no es fácil proporcionar una seguridad similar en un sistema distribuido. En la mayoría de los casos, el uso de transacciones distribuidas resulta muy problemático para coordinar los cambios de estado.

En su lugar, puede que tengas que empezar a utilizar conceptos como sagas (algo que detallaré ampliamente en el Capítulo 6) y consistencia eventual para gestionar y razonar sobre el estado en tu sistema. Estas ideas pueden requerir cambios fundamentales en la forma de pensar sobre los datos en tus sistemas, algo que puede resultar bastante desalentador al migrar sistemas existentes. Una vez más, ésta es otra buena razón para ser prudente en cuanto a la rapidez con que descompones tu aplicación. Adoptar un enfoque incremental de la descomposición, de modo que puedas evaluar el impacto de los cambios en tu arquitectura en producción, es realmente importante.

¿Debería utilizar microservicios?

A pesar de el impulso de algunos sectores para hacer de las arquitecturas de microservicios el enfoque por defecto para el software, creo que debido a los numerosos retos que he esbozado, su adopción sigue requiriendo una reflexión cuidadosa. Tienes que evaluar tu propio espacio de problemas, habilidades y panorama tecnológico, y comprender lo que intentas conseguir antes de decidir si los microservicios son adecuados para ti. Son un enfoque arquitectónico, no el enfoque arquitectónico. Tu propio contexto debe desempeñar un papel muy importante en tu decisión de seguir ese camino.

Dicho esto, quiero esbozar algunas situaciones que normalmente me inclinarían a no elegir microservicios, o a elegirlos.

Para quién podrían no funcionar

Dada la importancia de definir unos límites de servicio estables, creo que las arquitecturas de microservicios suelen ser una mala elección para productos o startups totalmente nuevos. En ambos casos, el dominio con el que trabajas suele experimentar cambios significativos a medida que iteras sobre los fundamentos de lo que intentas construir. Este cambio en los modelos de dominio provocará, a su vez, más cambios en loslímites de los servicios, y coordinar los cambios a través delos límites de los servicios es unaempresa costosa. En general, creo que es más apropiado esperar a que se haya estabilizado lo suficiente el modelo de dominio antes de intentar definir los límites de los servicios.

Veo la tentación de que las startups se decanten primero por los microservicios, con el razonamiento de: "¡Si tenemos éxito de verdad, necesitaremos escalar!". El problema es que no sabes necesariamente si alguien va a querer utilizar tu nuevo producto. E incluso si tienes el éxito suficiente para necesitar una arquitectura altamente escalable, lo que acabes ofreciendo a tus usuarios puede ser muy diferente de lo que empezaste a construir en un principio. Uber se centró inicialmente en las limusinas, y Flickr surgió de los intentos de crear un juego online multijugador. El proceso de encontrar el encaje del producto en el mercado significa que al final puedes acabar con un producto muy diferente del que pensabas construir cuando empezaste.

Las startups también suelen tener menos gente disponible para construir el sistema, lo que crea más retos con respecto a los microservicios. Los microservicios traen consigo nuevas fuentes de trabajo y complejidad, y esto puede inmovilizar un valioso ancho de banda. Cuanto más pequeño sea el equipo, más pronunciado será este coste. Cuando trabajo con equipos más pequeños con sólo un puñado de desarrolladores, dudo mucho en sugerir microservicios por esta razón.

El reto de los microservicios para las startups se ve agravado por el hecho de que normalmente su mayor limitación son las personas. Para un equipo pequeño, una arquitectura de microservicios puede ser difícil de justificar porque se necesita trabajo sólo para gestionar la implementación y la gestión de los microservicios en sí. Algunas personas han descrito esto como el "impuesto de los microservicios". Cuando esa inversión beneficia a mucha gente, es más fácil de justificar. Pero si una persona de tu equipo de cinco está dedicando su tiempo a estas cuestiones, es mucho tiempo valioso que no se emplea en construir tu producto. Es mucho más fácil pasarse a los microservicios más adelante, después de comprender dónde están las limitaciones en tu arquitectura y cuáles son tus puntos de dolor: entonces podrás centrar tu energía en utilizar los microservicios en los lugares más sensatos.

Por último, las organizaciones que crean software que será implementado y gestionado por sus clientes pueden tener problemas con los microservicios. Como ya hemos dicho, las arquitecturas de microservicios pueden introducir mucha complejidad en el ámbito de la implementación y el funcionamiento. Si tú mismo gestionas el software, puedes compensar esta nueva complejidad adoptando nueva tecnología, desarrollando nuevas habilidades y cambiando las prácticas de trabajo. Esto no es algo que puedas esperar que hagan tus clientes. Si están acostumbrados a recibir tu software como un instalador de Windows, les va a resultar muy chocante cuando les envíes la siguiente versión de tu software y les digas: "¡Pon estos 20 pods en tu clúster de Kubernetes!". Lo más probable es que no tengan ni idea de lo que es un pod, Kubernetes o un clúster.

Dónde funcionan bien

Según mi experiencia, probablemente la principal razón por la que las organizaciones adoptan microservicios es permitir que más desarrolladores trabajen en el mismo sistema sin estorbarse unos a otros. Si tu arquitectura y tus límites organizativos son correctos, permitirás que más personas trabajen independientemente unas de otras, reduciendo la contención en la entrega. Es probable que una startup de cinco personas encuentre un lastre en una arquitectura de microservicios. Es probable que una empresa de cien personas que crece rápidamente encuentre que su crecimiento es mucho más fácil de acomodar con una arquitectura de microservicios correctamente alineada en torno a sus esfuerzos de desarrollo de productos.

Las aplicaciones de Software como Servicio (SaaS) son, en general, también una buena opción para una arquitectura de microservicios. Normalmente, se espera que estos productos funcionen 24 horas al día, 7 días a la semana, lo que supone un reto a la hora de introducir cambios. La capacidad de liberación independiente de las arquitecturas de microservicios es una gran ayuda en este ámbito. Además, los microservicios pueden ampliarse o reducirse según sea necesario. Esto significa que, a medida que estableces una línea de base razonable para las características de carga de tu sistema, obtienes más control para garantizar que puedes escalar tu sistema de la forma más rentable posible.

La naturaleza agnóstica de la tecnología de los microservicios garantiza que puedas sacar el máximo partido de las plataformas en la nube. Los proveedores de nubes públicas ofrecen una amplia gama de servicios y mecanismos de implementación para tu código. Puedes hacer coincidir mucho más fácilmente los requisitos de servicios específicos con los servicios en la nube que mejor te ayudarán a implementarlos. Por ejemplo, puedes decidir desplegar un servicio como un conjunto de funciones, otro como una máquina virtual (VM) gestionada, y otro en una plataforma gestionada como servicio (PaaS).

Aunque vale la pena señalar que adoptar una amplia gama de tecnologías a menudo puede ser un problema, poder probar nuevas tecnologías con facilidad es una buena forma de identificar rápidamente nuevos enfoques que puedan reportar beneficios. La creciente popularidad de las plataformas FaaS es un ejemplo de ello. Para las cargas de trabajo adecuadas, una plataforma FaaS puede reducir drásticamente la cantidad de sobrecarga operativa, pero en la actualidad, no es un mecanismo de implementación que sea adecuado en todos los casos.

Los microservicios también presentan claras ventajas para las organizaciones que buscan prestar servicios a sus clientes a través de una variedad de nuevos canales. Muchos de los esfuerzos de transformación digital parecen consistir en intentar desbloquear funciones ocultas en los sistemas existentes. El deseo es crear nuevas experiencias de cliente que puedan satisfacer las necesidades de los usuarios a través de cualquier mecanismo de interacción que tenga más sentido.

Por encima de todo, una arquitectura de microservicios es una arquitectura que puede darte mucha flexibilidad mientras sigues haciendo evolucionar tu sistema. Esa flexibilidad tiene un coste, por supuesto, pero si quieres mantener abiertas tus opciones respecto a los cambios que puedas querer hacer en el futuro, puede ser un precio que merezca la pena pagar.

Resumen

Las arquitecturas de microservicios pueden darte un enorme grado de flexibilidad a la hora de elegir la tecnología, gestionar la robustez y el escalado, organizar los equipos y mucho más. Esta flexibilidad es en parte la razón por la que muchas personas están adoptando las arquitecturas de microservicios. Pero los microservicios conllevan un grado significativo de complejidad, y tienes que asegurarte de que esta complejidad está justificada. Para muchos, se han convertido en una arquitectura de sistema por defecto, que se utilizará en prácticamente todas las situaciones. Sin embargo, sigo pensando que son una elección arquitectónica cuyo uso debe estar justificado por los problemas que intentas resolver; a menudo, enfoques más sencillos pueden ofrecer resultados mucho más fácilmente.

Sin embargo, muchas organizaciones, especialmente las más grandes, han demostrado lo eficaces que pueden ser los microservicios. Cuando los conceptos básicos de los microservicios se comprenden e implementan adecuadamente, pueden ayudar a crear arquitecturas potentes y productivas que ayuden a los sistemas a ser más que la suma de sus partes.

Espero que este capítulo haya servido como una buena introducción a estos temas. A continuación, vamos a ver cómo definimos los límites de los microservicios, explorando por el camino los temas de la programación estructurada y el diseño orientado al dominio.

1 Este concepto fue esbozado por primera vez por David Parnas en "Information Distribution Aspects of Design Methodology", Information Processing: Actas del Congreso IFIP 1971 (Amsterdam: North-Holland, 1972), 1:339-44.

2 Alistair Cockburn, "Arquitectura hexagonal", 4 de enero de 2005, https://oreil.ly/NfvTP.

3 Para una introducción en profundidad al diseño basado en dominios, consulta Domain-Driven Design de Eric Evans (Addison-Wesley)-o para una visión general más condensada, consulta Domain-Driven Design Distilled de Vaughn Vernon (Addison-Wesley).

4 Matthew Skelton y Manuel Pais, Team Topologies (Portland, OR: IT Revolution, 2019).

5 David Heinemeier Hansson, "El monolito majestuoso", Signal v. Noise, 29 de febrero de 2016, https://oreil.ly/WwG1C.

6 Para obtener información útil sobre las ideas que subyacen al uso por Shopify de un monolito modular en lugar de microservicios, consulta "Deconstructing the Monolith", de Kirsten Westeinde.

7 Leslie Lamport, mensaje de correo electrónico a un tablón de anuncios del DEC SRC a las 12:23:29 PDT del 28 de mayo de 1987.

8 Microsoft Research ha realizado estudios en este espacio, y los recomiendo todos, pero como punto de partida, sugiero "¡No toques mi código! Examining the Effects of Ownership on Software Quality" de Christian Bird et al.

Get Construyendo Microservicios, 2ª Edición 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.