El decálogo de desastres en microservicios
Cómo las buenas intenciones técnicas pueden acabar en complejidad incontrolable.
Cuando Martin Fowler publicó su famoso artículo sobre microservicios en 2014, muchos equipos ya estaban construyendo arquitecturas orientadas a servicios. Pero aquel post — y el hype que le siguió — se coló en prácticamente todos los equipos de software del planeta. El “stack de Netflix” era lo más cool de la época: una forma de aprovechar las lecciones aprendidas por Netflix sobre sistemas distribuidos. Más de una década después, casi todas las ofertas de ingeniería de software mencionan “arquitectura de microservicios” como si fuera la norma.
Este artículo es una traducción y adaptación de otros dos que escribí, en inglés:
Hype Driven Development
A principios de la década de 2010, muchas organizaciones estaban desesperadas por mejorar su ciclo de desarrollo. Los equipos con decenas o cientos de ingenieros sufrían entornos pesados, pruebas lentas y despliegues programados. El libro “Continuous Delivery” de Fowler ayudó a poner luz sobre esos problemas y muchos se dieron cuenta de que sus monolitos majestuosos estaban generando cuellos de botella organizativos.
Los microservicios sonaban a solución perfecta: más pequeños, más ágiles, más modernos. Era más fácil empezar con una entrega continua desde cero que intentar introducirla en un monstruo de 200.000 líneas de código.
Así, los equipos comenzaron a dividir sus sistemas en tres, diez o cien microservicios. Casi todos hablaban entre sí usando “JSON sobre HTTP” (otros lo llamaban RESTful 😉). Parecía sencillo y, de repente, podían desplegar en producción en menos de 15 minutos. Se acabó aquello de “el equipo A ha roto la CI y no puedo desplegar”. Pura felicidad.
Hasta que llegó el baño de realidad. Resolver un problema organizativo a nivel de arquitectura también introducía complejidad. Una distinta. Las falacias de los sistemas distribuidos empezaron a hacerse muy reales: latencias, caídas parciales, consistencia eventual, logs imposibles de seguir. Lo que antes era un único sistema estable se convertía en un ecosistema de piezas que fallaban por separado. Y, como no, todas a la vez.
#1 Servicios demasiado pequeños
La posibilidad de crear nuevos servicios cada día desató la creatividad.Una nueva funcionalidad: ¡bam! Un servicio nuevo. En poco tiempo, equipos de 20 personas mantenían 50 microservicios. Uno por cabeza… y algunos sobraban.
Cada servicio tenía su propio ciclo de vida, dependencias, versiones y frameworks. Propagar una actualización de librerías o frameworks era una pesadilla. Peor aún: algunos servicios dependían de que otros se desplegaran a la vez. Y hubo quien incluso escribió microservicios solo para generar CSV. Sí, un network hop para un fichero de texto.

Lo llamamos servicitis: una fiebre que multiplica servicios sin control y hace imposible razonar sobre el sistema. Abrir el IDE ya no bastaba. Había que tener seis proyectos abiertos para entender algo.
#2 Entornos de desarrollo imposibles
¿Quién no ha escuchado esto?
“Oye, necesitamos arreglar los entornos de desarrollo, la gente se está quejando todo el tiempo.”
En sistemas distribuidos, los entornos de desarrollo son una pesadilla cara y frágil:
¿Cuánto cuesta levantar 200 servicios en la nube?
¿Y mantenerlos sincronizados con producción?
¿Qué pasa con los datos de prueba, coherentes entre los servicios?
¿Y con las feature flags y las configuraciones por región?
Hacerlo bien requiere una infraestructura de lujo que pocas empresas pueden permitirse. Y hacerlo mal significa frustrar a los equipos día sí, día también.
#3 Los test end-to-end
Antes, con un monolito, bastaba con un entorno de staging y un par de baterías de pruebas de Selenium para dormir tranquilo. Con microservicios, eso murió. Ahora solo puedes afirmar que “una combinación concreta de versiones y configuraciones funciona hoy”. Mañana, quién sabe.
Era muy difícil convencer a la gente de que no podíamos tener más que un par de estas pruebas. Encima, muchos equipos seguían corriéndolas en CI, una vez al día, como si eso sirviera de algo. La realidad es que deberían ejecutarse de forma continua, incluso en producción, como defendía Cindy Sridharan en su mítico artículo “Testing in production, the safe way”.
#4 La gran base de datos compartida
El paso lógico para “romper el monolito sin romper los datos” fue mantener una base de datos común. Parecía lo más fácil: menos carga operativa, consistencia asegurada… hasta que no. ¿Un usuario por servicio? ¿Permisos granulares por tabla? ¿Qué pasa si alguien borra un índice? ¿O si un servicio empieza a escribir donde no debe?
La base de datos compartida se convirtió en un cuello de botella y, muchas veces, en el verdadero monolito escondido. Migrar o desentrelazar todo eso, años después, fue una pesadilla en diferido.
#5 El API Gateway como el nuevo monolito
El API Gateway suele presentarse como una capa “neutral”: autenticar, enrutar y poco más. En la práctica, acaba siendo el lugar donde todo el mundo pone lo que no sabe dónde encajar.
Y llegan las agregaciones específicas para el frontend, las excepciones “temporales” que nunca se van, los versionados ad hoc, etc. Cada equipo empuja un poco porque es más barato hacerlo ahí que tocar su servicio. El resultado no es solo técnico, es organizativo:
Un equipo central que se convierte en cuello de botella, o un gateway “de todos” que en la práctica no es de nadie
Cambios que requieren alineamiento transversal, pero sin un responsable claro de aceptar el riesgo
Un sistema cuyo ritmo de evolución queda limitado por el riesgo percibido de modificar el gateway
Lo que debía ser una capa ligera terminaba convirtiéndose en un cuello de botella crítico. Y, por supuesto, en un único punto de fallo.
#6 El gateway como hotspot operativo
Autenticación, autorización, validación de tokens, multitenancy, rate limiting, traducción de protocolos. Cada request ejecuta lógica cara, a menudo con criptografía, sobre runtimes generalistas y configuraciones por defecto.
Aquí el problema no es el ownership, es la física del sistema:
thread pools que se agotan
I/O bloqueante en el peor sitio posible
Timeouts mal calibrados que provocan fallos en cascada
He visto gateways caer sin un solo bug. Simplemente llegó más tráfico del esperado y nadie había tocado la configuración. El sistema no falló: hizo exactamente lo que le pedimos hasta que ya no pudo más.
Construir gateways fiables no es cuestión de YAML ni de declaraciones de intención. Es entender cómo se comporta el sistema cuando la presión no baja: qué pasa con las colas, dónde se acumulan las esperas y cómo reacciona el runtime cuando todo va justo. Y eso, casi siempre, se descubre demasiado tarde.
#7 Timeouts, reintentos y resiliencia
Los sistemas distribuidos están en estado de fallo parcial constante. Cuando el servicio A no puede comunicarse con el B, la reacción natural es “hagamos un reintento”. Pero ese pequeño gesto te mete en la madriguera del conejo.Aumentar los timeouts, ignorar colas saturadas o malconfigurar los thread pools puede convertir un fallo puntual en un colapso total.
He visto equipos generar sus propias denegaciones de servicio distribuidas (DDoS) desde los móviles de los usuarios porque sus reintentos de conexión no tenían backoff ni jitter. Y sí, todo empezó con “solo vamos a reintentar tres veces”.
#8 Más servicios que ingenieros
He perdido la cuenta de los equipos con más servicios que personas. Y no me refiero a “un poco más”, sino a cuatro o cinco servicios por ingeniero. Sobre el papel suena bien: “hemos modularizado todo”. En la práctica, significa que cada persona es dueña, operadora y responsable de media docena de sistemas distribuidos.
En gigantes como Google o Uber, con plataformas internas potentes y automatización avanzada, puede funcionar. En el resto del mundo, es un desastre a cámara lenta: cada nuevo servicio añade sobrecarga cognitiva, pipelines, dashboards, alerts, secrets y dependencias. Cuando llega una reorg, esos servicios se quedan huérfanos, corriendo solos en la oscuridad.
#9 El zoo tecnológico
Muchas empresas dicen valorar la autonomía de ingeniería, pero pocas se detienen a definir qué significa en la práctica y qué límites tiene en su contexto. Cuando se ejerce sin fricción ni criterios compartidos, la autonomía no suele traducirse en decisiones explícitas, sino en acumulación: un servicio en Kotlin porque era lo más cómodo en ese momento, otro en Go por rendimiento, otro en Node por rapidez, varias bases de datos distintas y, con el tiempo, sistemas que solo una persona sabe operar con soltura.
No es dejadez ni falta de profesionalidad, sino una consecuencia natural de equipos bienintencionados que optimizan localmente. El problema es que cada stack nuevo añade carga operativa, superficie de ataque y coste de incorporación, y ese coste rara vez es visible al principio.
Suele aparecer más tarde, cuando hay que rotar equipos, integrar a gente nueva o modificar servicios que llevan años funcionando sin tocar. Es entonces cuando esos sistemas, que parecían razonables de forma aislada, se convierten en puntos frágiles del conjunto.
Con el tiempo, muchas organizaciones han empezado a introducir mecanismos de contención como catálogos internos, revisiones de arquitectura o tech radars. No eliminan la entropía, pero ayudan a hacerla visible antes de que se convierta en un problema sistémico.
#10 Cuando el organigrama dicta la arquitectura
Relacionado con el anterior, pero aún más perverso: diseñar tu arquitectura en función del organigrama. Imagina la situación: tenemos un equipo de “Pagos” con su namespace en Kubernetes, su stack de Terraform y sus alarmas en su canal de Slack. Todo limpio… hasta la próxima reorg.
Entra un nuevo VP con nuevas ideas y una nueva estructura. De repente, “Pagos” y “Suscripciones” se separan, pero toda la infraestructura sigue compartida. Opciones: compartirlo todo (y crear un infierno de dependencias) o migrarlo todo (un proyecto de seis meses sin valor para el usuario).
Así nace una de las deudas técnicas más costosas: el drift arquitectónico provocado por la evolución organizativa. Y sí, suele triplicar cualquier presupuesto no planificado.
La nueva normalidad
Han pasado más de diez años y muchos de los problemas se repiten. Cambian los frameworks y el tooling, pero no la naturaleza del sistema. Seguimos construyendo sistemas distribuidos y siguen comportándose como tales.
Ahora el foco está en los agentes de IA: componentes con estado, que toman decisiones y se comunican entre sí. El paralelismo es evidente. De nuevo aparecen los mismos puntos ciegos: latencia, consistencia, observabilidad, comportamiento no determinista. El hecho de que el componente “razone” no elimina ninguno de ellos.
El patrón suele ser el mismo: entusiasmo inicial, soluciones creativas para problemas reales y, con el tiempo, una acumulación de complejidad difícil de revertir. No es una cuestión de modas, sino de límites.
Seguiremos aprendiendo, como siempre. Y, como casi siempre, lo haremos después de que los sistemas fallen de formas que ahora mismo parecen obvias.
— João

