En el mundo del desarrollo de software, es fácil tener la impresión de que una aplicación moderna debería estar compuesta por microservicios. Un servicio para gestionar usuarios, otro para pedidos, otro para pagos y uno más para notificaciones. Cada elemento puede desarrollarse de forma independiente, implementarse por separado y escalarse cuando realmente lo necesita.
Sobre el diagrama, todo parece perfecto. Cada parte del sistema tiene sus tareas, los equipos pueden trabajar en paralelo y el fallo de un servicio no debería detener toda la aplicación.
El problema empieza cuando el diagrama se enfrenta a la realidad. En lugar de una aplicación, el equipo mantiene una docena de servicios, varios mecanismos de comunicación, numerosos procesos de implementación y registros, además de monitorización, permisos y configuraciones. Una operación empresarial sencilla requiere la comunicación entre varios componentes. Las pruebas se vuelven más difíciles, y encontrar la causa de un error exige seguir todo el flujo a través del sistema.
¿Significa eso que los microservicios son una mala solución? No. En las condiciones adecuadas, pueden mejorar considerablemente la forma de desarrollar productos grandes. Sin embargo, no son una etapa automática en la maduración de una aplicación ni garantizan una mejor escalabilidad.
Una buena arquitectura no es la que está compuesta por el mayor número de servicios. Es la que permite desarrollar el producto sin aumentar innecesariamente los costes, los riesgos y la complejidad.
Por eso, antes de tomar una decisión, conviene entender en qué se diferencian el monolito, el monolito modular y los microservicios, qué problemas resuelven y qué nuevas responsabilidades conllevan.
¿Qué es exactamente un monolito?
Un monolito es una aplicación cuyos principales componentes se desarrollan e implementan como una sola unidad. Puede contener numerosos módulos, gestionar distintos procesos empresariales y utilizar una arquitectura interna compleja. Por tanto, el hecho de que un sistema sea monolítico no significa que todo el código esté en un solo archivo ni que la aplicación sea caótica.
Un ejemplo podría ser un sistema que gestione ventas, clientes, productos, pedidos, pagos e informes. Todas estas funciones pueden formar parte de una sola aplicación, que utiliza una base de datos compartida y se implementa como un único producto.
Esta estructura tiene ventajas concretas. En muchos casos, es más fácil de iniciar, probar e implementar. Las llamadas entre módulos pueden realizarse directamente dentro del proceso de la aplicación, sin tener que enviar solicitudes a través de la red. Las transacciones que abarcan varias áreas del sistema pueden ser más sencillas de ejecutar si utilizan una base de datos compartida.
Un monolito también puede reducir la cantidad de elementos de infraestructura que hay que gestionar. No es necesario configurar procesos de implementación, monitorización y comunicación independientes para cada función pequeña.
Por supuesto, un monolito también tiene limitaciones. Si durante años se añaden nuevas funciones sin controlar las dependencias, el código se vuelve cada vez más difícil de entender. Un cambio en un lugar puede causar problemas en otro, y la implementación de una función pequeña requiere probar y publicar toda la aplicación.
Sin embargo, esto no es una característica inevitable del monolito. A menudo, el problema no es la forma de implementar la aplicación, sino su estructura interna.
No todos los monolitos son iguales
En los debates sobre arquitectura, a menudo se meten todas las aplicaciones monolíticas en el mismo saco. Sin embargo, existe una diferencia importante entre un sistema cuyos módulos tienen límites claramente definidos y una aplicación en la que cada elemento puede depender libremente de los demás.
En el primer caso, tenemos una estructura ordenada. En el segundo, puede surgir el llamado big ball of mud, es decir, un sistema con límites poco claros, numerosas dependencias y una lógica difícil de reconstruir.
Imaginemos una aplicación de ventas en la que los módulos de clientes, productos, pedidos y pagos tienen responsabilidades propias. El módulo de pedidos utiliza una interfaz específica del módulo de clientes, en lugar de recurrir a fragmentos aleatorios de su implementación interna. Los cambios en un área no deberían exigir modificaciones descontroladas en todo el sistema.
En cambio, si cada módulo modifica directamente los datos de los demás, utiliza sus clases internas y da por sentados detalles de su funcionamiento, la aplicación va perdiendo poco a poco su estructura.
Es aquí donde aparece el monolito modular.
Monolito modular: un compromiso que vale la pena considerar
El monolito modular sigue siendo una única aplicación que se implementa como una unidad, pero su interior está dividido en módulos claramente definidos. Cada uno se ocupa de un área empresarial concreta, tiene límites claramente delimitados y ofrece a otras partes del sistema una forma controlada de colaborar.
En la práctica, esto puede traducirse en módulos responsables de los clientes, el catálogo de productos, los pedidos, la facturación o las notificaciones. Cada uno tiene su propia lógica y el acceso a sus mecanismos internos está restringido.
La diferencia más importante es que los límites lógicos no tienen por qué implicar límites de infraestructura. Los módulos pueden estar bien separados en el código, aunque la aplicación siga ejecutándose e implementándose como un todo.
Este modelo permite organizar las responsabilidades, reducir las dependencias y preparar el sistema para seguir desarrollándose sin introducir todos los costes propios de una arquitectura distribuida.
El monolito modular puede ser una buena solución, en particular, cuando:
-
el producto lo desarrolla un solo equipo o varios equipos que colaboran estrechamente;
-
los límites empresariales del sistema todavía se están definiendo;
-
la mayoría de las funciones utilizan los mismos datos y deben mantener su coherencia;
-
no es necesario escalar de forma independiente las distintas áreas;
-
el proceso de implementación actual funciona lo suficientemente bien;
-
la empresa quiere conservar la posibilidad de separar determinados módulos más adelante.
Esto último es especialmente importante. Empezar con un monolito modular no significa renunciar a los microservicios. Si los límites de los módulos están bien diseñados y cambian las necesidades empresariales, en el futuro se puede separar un área concreta en un servicio independiente.
Sin embargo, no se trata de una migración automática que se haga con un solo clic. Hay que tener en cuenta el almacenamiento de los datos, las dependencias, las transacciones, la comunicación y la responsabilidad de mantener el nuevo componente. Aun así, una buena modularidad puede facilitar considerablemente el proceso.
Microservicios: ¿qué aportan realmente?
La arquitectura de microservicios divide la aplicación en servicios más pequeños, que realizan funciones empresariales concretas y pueden desarrollarse e implementarse de forma independiente. Los servicios se comunican entre sí mediante interfaces definidas, a menudo a través de API, mensajes o eventos.
Gracias a esto, el equipo puede cambiar un servicio sin tener que publicar toda la aplicación. Los distintos componentes también pueden utilizar tecnologías diferentes o escalarse de forma independiente si sus cargas y requisitos realmente difieren.
Por ejemplo, una plataforma de comercio electrónico puede tener un servicio de búsqueda de productos cuya carga crezca mucho más rápido que la del módulo de gestión de cuentas de usuario. En una arquitectura de microservicios, se pueden aumentar los recursos del servicio de búsqueda sin tener que escalar todos los demás elementos del sistema.
Algo similar puede ocurrir cuando un área del producto requiere despliegues especialmente frecuentes, otra tiene requisitos de disponibilidad distintos y otra más está desarrollada por un equipo independiente.
Por tanto, los microservicios pueden ayudar en varias áreas concretas:
Despliegues independientes. Un cambio en un servicio no tiene por qué exigir el lanzamiento de todos los demás componentes, siempre que sus contratos sigan siendo compatibles.
Escalado independiente. Se pueden aumentar los recursos únicamente de los servicios que lo necesiten, en lugar de escalar todo el sistema.
Responsabilidades diferenciadas entre equipos. Los equipos pueden desarrollar determinadas áreas del producto de forma autónoma si los límites de responsabilidad están realmente bien diseñados.
Aislamiento de algunos problemas. Un servicio bien diseñado puede limitar el impacto de ciertos fallos en el resto del sistema. Sin embargo, esto no ocurre automáticamente. Si los demás componentes no pueden funcionar sin un servicio que no está disponible, el problema aún puede afectar a una parte considerable de la aplicación.
Flexibilidad tecnológica. Cada servicio puede adaptarse a distintas necesidades técnicas, aunque mantener varias tecnologías también aumenta los costes.
Cabe señalar que ninguno de estos beneficios depende únicamente del número de servicios. Requieren límites arquitectónicos adecuados, automatización, monitorización, procesos de pruebas y competencias del equipo.
El precio de dividir el sistema: ¿qué queda fuera del diagrama?
El mayor error al planificar microservicios es compararlos únicamente en función de la estructura del código. Dividir una aplicación no solo cambia la forma de implementarla, sino también de ejecutarla, probarla, desplegarla y diagnosticar los problemas.
En un monolito, una función puede llamar a otra dentro del mismo proceso. En un sistema distribuido, la comunicación entre servicios suele realizarse a través de la red, lo que puede sufrir retrasos, fallar o interrumpirse en un momento inesperado.
Surgen nuevas preguntas. ¿Qué hacer si el servicio A ha realizado una operación, pero el servicio B no ha recibido la respuesta? ¿Reintentar la solicitud no provocará que la misma operación se ejecute dos veces? ¿Qué ocurrirá si una parte del proceso finaliza correctamente, pero los demás elementos no registran el cambio?
No se trata de problemas infrecuentes ni teóricos. En sistemas que gestionan pagos, pedidos, reservas o existencias, pueden provocar discrepancias empresariales reales.
A esto se suman los costes de mantenimiento:
-
más componentes que monitorizar;
-
configuraciones y procesos de despliegue independientes;
-
la necesidad de rastrear las solicitudes que pasan por varios servicios;
-
mecanismos adicionales de comunicación y gestión de errores;
-
pruebas de integración y de contratos;
-
gestión de las versiones de la API;
-
control de acceso entre componentes;
-
un mayor número de posibles puntos de fallo;
-
la necesidad de contar con conocimientos sobre sistemas distribuidos.
Esto no significa que los microservicios siempre sean más caros a lo largo de todo su ciclo de vida. En un sistema suficientemente grande, pueden reducir los costes de coordinación y acelerar el desarrollo. Sin embargo, el coste operativo inicial es real y debe tenerse en cuenta al tomar la decisión.
Si la empresa no dispone de automatización de despliegues, una buena monitorización y procedimientos para responder a los fallos, dividir la aplicación puede aumentar la complejidad más rápido de lo que aporta beneficios.
¿Cuándo empiezan a tener sentido los microservicios?
No existe una cifra universal de usuarios, solicitudes o desarrolladores que, al superarse, implique que haya que pasar a los microservicios. La decisión depende del tipo de producto, su carga, la estructura organizativa y las limitaciones reales de la arquitectura actual.
Sin embargo, se pueden señalar situaciones en las que conviene plantearse seriamente dividir una parte del sistema.
1. Las distintas áreas tienen perfiles de carga claramente diferentes.
Si una función requiere mucha más capacidad de procesamiento que las demás, el escalado independiente puede aportar beneficios tangibles. No obstante, hay que confirmar que el problema no se pueda resolver con métodos más sencillos, como optimizar las consultas, usar una caché o una cola de tareas, o aumentar los recursos de toda la aplicación.
2. Los equipos necesitan desarrollar distintas áreas de forma independiente.
En una organización grande, una base de código compartida y un único proceso de lanzamiento pueden dificultar el trabajo de muchos equipos. Separar las responsabilidades puede mejorar su autonomía, siempre que los servicios tengan límites estables y contratos claramente definidos.
3. Las distintas funciones tienen requisitos de disponibilidad diferentes.
Algunas áreas del producto pueden requerir una especial resistencia a los fallos, mientras que otras pueden dejar de estar disponibles temporalmente sin detener los procesos más importantes. Los servicios independientes permiten adaptar la estrategia de escalado, despliegue y recuperación a cada área concreta.
4. Un módulo requiere una tecnología o un método de procesamiento distintos.
A veces, una tarea concreta tiene requisitos justificados que sería ineficiente satisfacer en la aplicación principal. En ese caso, separarla puede limitar el impacto de esos requisitos en el resto del producto. Sin embargo, conviene recordar que una pila tecnológica adicional también implica una responsabilidad adicional de mantenimiento.
5. Los límites del negocio ya están bien definidos.
Si se sabe dónde termina la responsabilidad de un área y empieza la de otra, es más fácil diseñar servicios independientes. Cuando los límites no están claros y cambian con frecuencia, definirlos prematuramente puede dar lugar a dependencias costosas y a cambios constantes en las interfaces.
El principio más importante es: separa un servicio porque resuelve un problema concreto, no porque otro elemento en el diagrama parezca moderno.
¿Cuándo será mejor elegir un monolito modular?
Un monolito modular suele ser la mejor opción cuando la empresa necesita organizar la aplicación, pero aún no hay motivos suficientes para distribuirla en varios servicios independientes.
Este puede ser el caso de un producto nuevo cuyo modelo de negocio aún está en desarrollo. Al principio es difícil prever qué funciones tendrán más carga, cuáles requerirán despliegues independientes y dónde acabarán situándose los límites de responsabilidad.
Algo parecido ocurre con los sistemas empresariales de tamaño medio en los que la mayoría de las operaciones afectan a datos compartidos. Dividir la lógica en varios servicios puede introducir problemas adicionales de coherencia de datos y transacciones sin aportar a cambio suficiente independencia.
También conviene plantearse un monolito modular cuando la empresa tiene recursos operativos limitados. Un único proceso de despliegue y un modelo de monitorización más sencillo pueden ser una solución más razonable que mantener una docena de servicios, cada uno con su propia configuración y sus propios procedimientos.
Esto no significa dejarlo todo en una sola aplicación sin un plan de futuro. Un monolito modular bien diseñado requiere disciplina: límites claros entre módulos, acceso controlado a los datos y limitación de dependencias innecesarias.
Si se respetan estos principios, la empresa puede beneficiarse de un modelo de despliegue más sencillo y, al mismo tiempo, preparar el sistema para separar determinadas funcionalidades en el futuro, si fuera necesario.
La escalabilidad no consiste solo en el número de servidores
Uno de los argumentos más habituales a favor de los microservicios es la escalabilidad. Sin embargo, conviene distinguir dos cuestiones diferentes: la escalabilidad técnica y la capacidad de la organización para desarrollar el producto.
La escalabilidad técnica consiste en poder gestionar una carga mayor. En muchos casos, una aplicación monolítica puede ejecutarse en varias instancias y utilizar balanceo de carga, caché, colas de tareas y una base de datos bien diseñada. Un sistema así puede gestionar un volumen de tráfico considerable sin necesidad de dividirlo en decenas de servicios.
Por supuesto, no todos los monolitos se pueden escalar de cualquier manera. Los problemas pueden deberse a que el estado se almacena en la memoria del proceso, a una base de datos compartida, a operaciones síncronas costosas o a las limitaciones de un componente concreto. No obstante, lo primero es determinar qué constituye realmente el cuello de botella.
La escalabilidad organizativa es otra cuestión. Si muchos equipos trabajan en el producto y habitualmente se bloquean entre sí al hacer cambios y desplegar, dividir las responsabilidades puede aportar beneficios incluso cuando el rendimiento del sistema es suficiente.
En la práctica, conviene basar la decisión en mediciones. ¿Tiene la aplicación problemas de rendimiento? ¿Los despliegues tardan demasiado? ¿Los equipos tienen que acordar cada cambio? ¿Una función concreta necesita recursos diferentes? ¿Las averías de un módulo afectan a los demás?
Las respuestas permiten distinguir un problema de arquitectura de uno de infraestructura, de calidad del código o de organización del trabajo.
Migración a microservicios: ¿por qué no conviene dividirlo todo de una vez?
Si la aplicación actual realmente empieza a limitar el desarrollo del producto, migrar a microservicios no tiene por qué implicar reescribir todo el sistema de una sola vez.
Un enfoque más seguro puede consistir en separar gradualmente áreas concretas. Primero hay que determinar qué módulo tiene responsabilidades lo bastante claras, qué datos gestiona y cómo colabora con las demás partes de la aplicación.
A continuación, se puede diseñar el límite de comunicación, preparar pruebas y transferir gradualmente las responsabilidades al nuevo servicio. Así es posible comprobar las hipótesis en un ámbito limitado antes de que la empresa se decida a realizar más cambios.
Por ejemplo, un sistema de ventas puede incluir inicialmente un módulo de generación de informes que carga mucho la base de datos y ralentiza las operaciones de los usuarios. En vez de dividir todo el sistema, primero se puede analizar si es posible trasladar la generación de informes a un proceso o servicio independiente, quizá utilizando datos preparados de forma asíncrona.
Esta separación tiene sentido si realmente reduce la carga de la aplicación principal o permite desarrollar y escalar la generación de informes de forma independiente. Sin embargo, si los informes son sencillos y su carga es baja, puede que el servicio adicional no sea necesario.
Por eso, la migración debería partir de un problema y un resultado esperado, no de la premisa de que todos los módulos tendrán que convertirse en microservicios tarde o temprano.
También conviene recordar que separar un servicio no consiste únicamente en trasladar el código. Hay que resolver cuestiones relacionadas con los datos, la comunicación, la autorización, la monitorización, los despliegues, las pruebas y la responsabilidad ante las averías. Solo entonces el nuevo componente se convierte en un elemento realmente independiente de la arquitectura.
¿Cómo tomar una decisión? Lista práctica de preguntas
Antes de elegir una arquitectura, conviene responder a varias preguntas. No sustituyen el análisis técnico, pero ayudan a ordenar la decisión.
1. ¿Qué problema concreto queremos resolver? ¿Se trata del rendimiento, el tiempo de despliegue, las dependencias entre equipos, la disponibilidad o la dificultad de mantener el código? Si no podemos identificar el problema, es difícil evaluar si los microservicios son la solución adecuada.
2. ¿El problema se debe realmente a la arquitectura? Una aplicación lenta quizá necesite optimizar las consultas, y los despliegues difíciles pueden deberse a la falta de pruebas y automatización. Un cambio de arquitectura no sustituye esas acciones.
3. ¿Están suficientemente claros los límites del negocio? Si los módulos utilizan constantemente los datos y las reglas internas de otros, separarlos en servicios independientes puede perpetuar una división incorrecta.
4. ¿Necesitamos escalar o desplegar de forma independiente? Si todas las partes del sistema cambian y soportan cargas de manera similar, el beneficio de la independencia puede ser reducido.
5. ¿Contamos con las herramientas y competencias necesarias para mantener un sistema distribuido? La monitorización, los registros, el seguimiento de solicitudes, los despliegues automatizados y los procedimientos de gestión de incidentes son elementos importantes de esta decisión.
6. ¿Cómo se gestionarán los datos y las transacciones? Separar un servicio puede dificultar mantener la atomicidad de operaciones que abarcan varias áreas. Hay que planificar qué ocurrirá si una parte del proceso tiene éxito y otra no.
7. ¿Los beneficios justifican el coste total? Conviene tener en cuenta no solo el coste de implementación, sino también el de la infraestructura, la monitorización, la seguridad, las pruebas, el mantenimiento y el desarrollo de competencias.
8. ¿Podemos empezar con un cambio menor? A veces basta con organizar mejor los módulos, separar un proceso asíncrono o mejorar la automatización de los despliegues. El cambio más pequeño que resuelva eficazmente el problema suele ser el mejor punto de partida.
Los errores más comunes al elegir una arquitectura
El primer error es equiparar modernidad con número de servicios. Los microservicios son una forma concreta de organizar un sistema, no un fin en sí mismos.
El segundo es diseñar la arquitectura basándose en previsiones que aún no se han confirmado en el funcionamiento real del producto. Conviene mantener la posibilidad de evolucionar y separar otros componentes cuando surja una necesidad justificada.
El tercero es dividir el sistema sin definir los límites del negocio. Si los servicios dependen mucho unos de otros, se crea una arquitectura distribuida que conserva los problemas del monolito, pero añade los costes de comunicación por red.
El cuarto es pasar por alto los aspectos operativos de la solución. Cada servicio nuevo debe desplegarse, monitorizarse, protegerse y diagnosticarse. Sin los procesos adecuados, un número creciente de componentes puede reducir la fiabilidad en vez de mejorarla.
El quinto es intentar resolver problemas organizativos solo con tecnología. Los microservicios pueden favorecer la autonomía de los equipos, pero no sustituyen una asignación clara de responsabilidades, la comunicación ni una buena gestión del producto.
El sexto error es tratar el monolito modular como una solución temporal que hay que abandonar lo antes posible. Una aplicación monolítica bien diseñada puede seguir siendo la opción adecuada durante muchos años. No es necesario complicar su arquitectura sin obtener un beneficio concreto.
La arquitectura debe evolucionar junto con el producto
No existe una arquitectura que sea la mejor para todas las aplicaciones. Un producto pequeño desarrollado por un equipo de unas pocas personas tiene necesidades distintas a las de una plataforma que da servicio a muchos dominios empresariales independientes y que desarrollan una docena de equipos.
Un monolito puede ofrecer simplicidad en los despliegues y facilitar la gestión operativa. Un monolito modular permite organizar la estructura de la aplicación y conservar esa simplicidad. Los microservicios, por su parte, pueden ofrecer independencia en el desarrollo, los despliegues y el escalado, si el producto y la organización realmente necesitan ese modelo.
Lo más importante es identificar el problema al que se enfrenta la empresa. No vale la pena introducir una docena de servicios solo porque así es como se ve la arquitectura de las plataformas populares. Tampoco conviene aferrarse a un monolito si sus limitaciones frenan el desarrollo del producto y se pueden señalar beneficios concretos al separar una parte del sistema.
Una buena decisión arquitectónica no consiste en prever todas las necesidades futuras. Consiste en construir una solución que responda a los requisitos actuales sin cerrar el camino a cambios razonables en el futuro.
Antes de dividir una aplicación en microservicios, comprueba si realmente necesitas servicios independientes o si, ante todo, necesitas un sistema mejor organizado. Esta diferencia puede determinar no solo la arquitectura, sino también los costes y el ritmo de desarrollo del producto.
Fuentes y lecturas adicionales
-
AWS Well-Architected Framework: Elige cómo segmentar tu carga de trabajo: compromisos entre la arquitectura monolítica y la de microservicios.
-
Microsoft Azure Architecture Center: Arquitectura de microservicios: complejidad, comunicación, pruebas y gestión de datos en sistemas distribuidos.
-
microservices.io: ¿Hasta qué punto puede ser modular tu monolito?: organización del monolito en torno a dominios empresariales.
Artículos relacionados de Web24
-
Tu aplicación tiene 5 años. ¿Sigue siendo apta para evolucionar?
-
¿Por qué cada nueva función en una aplicación antigua cuesta cada vez más?
-
Tu aplicación funciona. Pero ¿se puede desplegar de forma segura 100 veces al día?
Los enlaces anteriores llevan a artículos sobre temas relacionados con la modernización, los costes de desarrollo y el proceso de despliegue. No sustituyen a un análisis específico sobre la elección de arquitectura.
Te invitamos a leerlos.
