Arquitectura de software
Microservicios: límites de servicio, despliegue independiente y su coste real
Intermedio12 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Los microservicios cambian complejidad interna por complejidad distribuida, y el cambio solo compensa cuando la organización necesita entregar de forma independiente. Esta guía define lo que el estilo exige realmente, muestra cómo encontrar límites que resistan el contacto con funcionalidades reales, compara los estilos de comunicación y ofrece una visión realista de la factura operativa y del camino de salida de un monolito.
Lo que aprenderás
- Enunciar las propiedades que debe tener un sistema antes de poder llamarlo microservicios con honestidad
- Encontrar los límites de los servicios a partir de las capacidades de negocio y no de las capas técnicas
- Elegir entre comunicación síncrona y comunicación basada en eventos para una interacción concreta
- Estimar la inversión operativa que exige una plataforma de microservicios antes de comprometerse con ella
- Planificar una extracción incremental desde un monolito con el enfoque strangler fig
Los microservicios suelen presentarse como una forma de construir software, pero se entienden mejor como una forma de organizar la entrega. La descripción técnica (servicios pequeños, cada uno en su propio proceso, que se comunican por la red) es la parte fácil, y describe muchísimos sistemas que no sacan ningún provecho de ello. Lo que hace que el estilo merezca la pena es una propiedad que no tiene nada que ver con el tamaño: cualquier servicio puede modificarse, probarse, desplegarse y revertirse sin coordinarse con los equipos responsables de los demás.
Esa propiedad sale cara. Exige pipelines automatizados por servicio, tolerancia a fallos parciales en cada llamante, una observabilidad que atraviese procesos y un diseño de datos en el que dos servicios nunca tocan las mismas filas. Los equipos que adoptan el vocabulario sin pagar por la propiedad acaban con un monolito distribuido: todos los saltos de red de los microservicios, toda la coordinación de entregas de un monolito y una experiencia de depuración peor que la de ambos. Esta guía trata de cómo ganarse esa propiedad con honestidad, y de los casos en los que un único desplegable bien modularizado es la mejor respuesta de ingeniería.
Lo que el estilo exige de verdad
Cinco compromisos separan los microservicios de «varias aplicaciones que se hablan entre sí».
Una unidad desplegable por servicio, que se entrega a su propio ritmo. Si publicar un cambio en el servicio de precios requiere una entrega sincronizada del servicio de catálogo, ambos son una única unidad de cambio disfrazada de dos contenedores. Poder desplegar de forma independiente es la prueba de fuego, y es lo que hay que medir.
Datos privados. Un servicio es dueño de su estado persistente y es el único proceso que lo lee o lo escribe. Los demás servicios solo ven ese estado a través de la interfaz del servicio o de los datos que este publica deliberadamente. Es la regla que más se incumple y aquella cuya violación cuesta más deshacer.
Un contrato que sobrevive a su implementación. Los consumidores dependen de una interfaz publicada (una API HTTP, un esquema de mensaje), no de una librería compartida de clases de entidad. La evolución compatible hacia atrás es un requisito de diseño, no una aspiración.
Aislamiento de fallos. Toda llamada remota puede ser lenta, fallar o devolver algo inesperado. Los llamantes fijan timeouts, limitan la concurrencia y se degradan con elegancia. Sin esto, un sistema de doce servicios con un 99,9 % de disponibilidad cada uno es menos fiable que el monolito al que sustituyó.
Un equipo responsable de principio a fin. Las mismas personas diseñan, construyen, despliegan y dan soporte al servicio. La ley de Conway convierte esto en algo más parecido a una predicción que a una preferencia: una organización cuyos equipos están divididos por capa técnica producirá servicios divididos por capa técnica, diga lo que diga el diagrama de arquitectura.
Encontrar límites que aguanten
Los malos límites son el fracaso característico de los proyectos de microservicios, y casi siempre se trazan siguiendo líneas técnicas: un «servicio de base de datos», un «servicio de validación», un «servicio de orquestación». Cada uno de ellos participa en todos los casos de uso, así que cada funcionalidad toca todos los servicios y nada puede entregarse por separado.
Los límites que aguantan se trazan alrededor de capacidades de negocio: cosas que hace el negocio y que tienen nombre, responsable y ciclo de vida propios. Pedidos, precios, inventario, facturación, seguimiento de envíos, identidad de clientes. El vocabulario y la técnica de modelado vienen del domain-driven design, donde un bounded context es exactamente la unidad que debe implementar un servicio.
Tres pruebas prácticas ayudan a juzgar un límite candidato:
| Prueba | Pregunta | Cómo es una mala respuesta |
|---|---|---|
| Cohesión del cambio | Cuando llega una funcionalidad típica, ¿cuántos servicios cambian? | Casi todos, siempre |
| Propiedad de los datos | ¿Puede este servicio responder a sus consultas principales desde su propio almacén? | Necesita un join entre otros dos servicios |
| Vocabulario | ¿Los términos que usa tienen un único significado coherente? | «Pedido» significa cuatro cosas distintas en un mismo servicio |
La prueba de cohesión del cambio es la más reveladora, porque se puede medir a posteriori. Toma las últimas veinte funcionalidades terminadas y cuenta cuántos servicios tocó cada una. En una distribución sana, la mayoría de las funcionalidades caen en uno o dos servicios. Si la mediana es cinco, los límites están mal y añadir más servicios lo empeorará.
Los límites también tienen una dimensión temporal. La división correcta para un producto con 30 000 usuarios rara vez es la correcta con 3 millones, y los servicios a veces se fusionan. Trata un límite como una hipótesis con fecha de caducidad y no como una decisión permanente, y mantén bajo el coste de fusionar dos servicios conservando interfaces estrechas.
Datos por servicio, y qué hacer con las consultas
Dar a cada servicio su propio esquema es sencillo hasta que el primer informe necesita datos de cuatro de ellos. Todas las respuestas disponibles son compromisos, y elegir mal aquí es lo que empuja a los equipos de vuelta a una base de datos compartida.
Composición de APIs. Un llamante (a menudo el gateway o un agregador dedicado) consulta varios servicios y hace el join en memoria. Es simple, correcto y válido para un puñado de registros. Se degrada mucho en cuanto hay que filtrar u ordenar entre servicios, porque no puedes paginar un join que calculas tú mismo.
Modelos de lectura replicados. Cada servicio publica eventos cuando cambian sus datos; los servicios interesados mantienen una copia local y desnormalizada, con la forma que necesitan sus consultas. Las lecturas pasan a ser rápidas y locales; el precio es la consistencia eventual y el código que mantiene la proyección. Es la forma general del patrón CQRS descrito en la guía de patrones de diseño de microservicios.
Un almacén analítico separado. Los informes y la business intelligence no deberían ejecutarse nunca contra los servicios operativos. Envía los cambios en streaming a un data warehouse o a un data lake y deja que los analistas hagan allí todos los joins que quieran.
La regla que mantiene esto bajo control: un servicio puede publicar sus datos, pero nunca deja que otro servicio acceda directamente a su almacenamiento. Una réplica de lectura abierta a otros equipos es una base de datos compartida con pasos extra: el esquema se vuelve público y se congela.
Estilos de comunicación
Dos familias, cada una adecuada para interacciones distintas.
La petición/respuesta síncrona (HTTP o gRPC) es la opción correcta cuando el llamante no puede continuar sin la respuesta: comprobar que un cliente existe antes de crear un pedido, validar un token. Resulta fácil de entender y fácil de trazar. Su peligro es el acoplamiento temporal (la disponibilidad del llamante pasa a ser el producto de la de todos los servicios de la cadena) y una latencia que se acumula salto a salto. Mantén las cadenas cortas: dos niveles de profundidad son manejables; cinco son un stack trace distribuido esperando su timeout.
Los eventos asíncronos publican un hecho y dejan que las partes interesadas reaccionen. «OrderPlaced» se emite una sola vez; inventario, facturación y notificaciones hacen cada uno su parte a su propio ritmo. El emisor no sabe ni le importa quién escucha, así que añadir un consumidor no requiere ningún cambio aguas arriba, y que un consumidor esté caído retrasa el trabajo en lugar de hacerlo fallar. Los costes son la consistencia eventual, una depuración de extremo a extremo más difícil y la necesidad de que los consumidores gestionen entregas duplicadas. La mecánica de los brokers, el orden y las garantías de entrega se explican en patrones y garantías de mensajería.
Una heurística útil: usa llamadas síncronas para las consultas y eventos asíncronos para los cambios de estado que se propagan. Los comandos que deben confirmarse a un usuario que espera siguen siendo síncronos; todo lo que el usuario no necesita esperar se convierte en un evento.
// Synchronous: the order cannot be priced without this answer.
CustomerTier tier = customerClient.tierFor(customerId); // timeout: 300 ms, fallback: STANDARD
// Asynchronous: nobody waits for stock, invoicing or email.
events.publish(new OrderPlaced(order.id(), order.lines(), order.total(), Instant.now()));
Sea cual sea el estilo, el versionado importa más que la elección del protocolo. Añade campos, nunca les cambies el significado; tolera los campos desconocidos en el lado del consumidor, y mantén legible la forma antigua de un mensaje al menos durante el tiempo que tarde en actualizarse el consumidor más lento.
La factura operativa
El esfuerzo de ingeniería que exige una plataforma de microservicios se subestima sistemáticamente porque no aparece en el diagrama de arquitectura. Antes de comprometerte, presupuéstalo entero.
- Un pipeline por servicio. Build, pruebas, análisis de seguridad, despliegue en cada entorno, rollback. Multiplícalo por el número de servicios; si crear un servicio nuevo cuesta una semana de fontanería de pipelines, los equipos evitarán crear servicios y meterán las funcionalidades en los que ya existen. Por eso los pipelines de entrega continua son un requisito previo y no un proyecto para después.
- Trazas distribuidas y logs correlacionados. Sin un trace ID que se propague por cada salto, diagnosticar «el checkout va lento» significa leer doce ficheros de log a mano.
- Métricas y alertas centralizadas por servicio, con objetivos de nivel de servicio (SLO) que describan el comportamiento que ve el usuario y no el uso de CPU.
- Plataforma de ejecución. Orquestación de contenedores, descubrimiento de servicios, gestión de secretos, rotación de certificados, políticas de red.
- Pruebas de contrato. Las pruebas de contrato guiadas por el consumidor detectan el cambio incompatible antes de que llegue a un entorno donde diagnosticarlo cuesta un día.
- Guardias y runbooks por equipo, porque el equipo que despliega es el equipo al que despiertan.
Una comprobación rápida: si la organización no es capaz de desplegar su monolito actual en producción cuando quiera, en menos de una hora y con pruebas automatizadas en las que confía, no está preparada para operar quince servicios. Los microservicios amplifican la madurez de un proceso de entrega; no la aportan.
Cuándo el monolito es la respuesta correcta
Un único desplegable es la opción correcta más a menudo de lo que sugieren las charlas de conferencias. Lo es cuando el equipo es lo bastante pequeño como para caber en una sola daily, cuando el dominio aún no se entiende lo suficiente como para trazar límites estables, cuando el tráfico del producto crece de forma uniforme y no en puntos calientes, y cuando la consistencia transaccional en todo el modelo es un requisito real y no una costumbre.
La versión a la que conviene aspirar es el monolito modular: un proceso, un despliegue, pero módulos internos con límites aplicados. Cada módulo tiene su propio árbol de paquetes, su propio esquema (o al menos sus propias tablas, sin claves foráneas entre módulos), una interfaz pública de un puñado de tipos y una comprobación en tiempo de build que hace fallar la compilación cuando un módulo mete mano en las tripas de otro. El sistema de módulos de Java, las reglas de ArchUnit o simplemente módulos de build separados sirven igual.
| Aspecto | Monolito modular | Microservicios |
|---|---|---|
| Despliegue | Un pipeline, un artefacto | Un pipeline por servicio |
| Refactorización entre límites | Asistida por el compilador, minutos | Entregas coordinadas, semanas |
| Consistencia | Transacciones locales | Sagas y consistencia eventual |
| Escalado | Toda la aplicación a la vez | Por servicio |
| Radio de impacto de un fallo | Todo el proceso | Un servicio, si está aislado |
| Autonomía de los equipos | Tren de entregas compartido | Independiente |
| Depuración | Un único stack trace | Traza distribuida |
El monolito modular es también el punto de partida honesto para un sistema que piensas dividir más adelante, porque los módulos te indican dónde están las costuras. Dividir un monolito bien modularizado es un trabajo mecánico; dividir uno enmarañado es arqueología.
Migrar con el strangler fig
Las reescrituras de sistemas grandes fracasan por razones bien conocidas: el negocio sigue cambiando el sistema antiguo mientras se construye el nuevo, y el paso final concentra un riesgo enorme en un único momento. El enfoque strangler fig evita ambas cosas extrayendo una capacidad cada vez, dirigiendo el tráfico a la nueva implementación y dejando que el sistema antiguo encoja hasta que se pueda retirar.
Pon una capa de enrutamiento delante
Coloca un gateway o un reverse proxy entre los clientes y el monolito para poder redirigir el tráfico ruta a ruta sin cambiar los clientes. Consulta gestión de APIs y gateways.Elige una capacidad que merezca la pena extraer
Escoge una con un límite claro, un problema real (cambia a menudo o escala de forma distinta) y poco enredo de datos. No empieces por la más difícil ni por la más central.Separa primero los datos
Identifica las tablas que pertenecen a la capacidad, elimina del monolito los joins y las claves foráneas entre esquemas y sustitúyelos por llamadas o eventos publicados. Suele ser el paso más largo y el que determina el éxito.Construye el servicio y ejecuta ambos
Implementa el nuevo servicio y ejecútalo junto al código antiguo (tráfico en sombra, o escritura en ambos y comparación) hasta que el comportamiento coincida.Cambia el tráfico y después borra
Mueve la ruta, vigila las métricas, mantén disponible el rollback durante una semana y después borra el código antiguo. Saltarse el borrado es la forma en que las organizaciones acaban manteniendo dos implementaciones para siempre.
Sigue dos cifras durante todo el proceso: la proporción de tráfico que atienden los servicios extraídos y el número de funcionalidades que todavía requieren un cambio en el monolito. Si la segunda no baja, la extracción está produciendo servicios sin producir autonomía.
Errores habituales
Dividir por capa técnica. Un «servicio de UI», un «servicio de negocio» y un «servicio de datos» recrean las capas de una sola aplicación a través de la red, y garantizan que cada funcionalidad toque los tres.
Empezar con demasiados servicios. Los equipos que arrancan un producto nuevo con veinte servicios están adivinando los límites antes de entender el dominio. Empieza con dos o tres servicios de grano grueso, o con un monolito modular, y divide cuando una presión concreta lo exija.
Librerías compartidas con lógica de dominio. Un JAR común con la entidad Order dentro ata a todos los servicios a un mismo ciclo de entregas. Comparte utilidades si no queda más remedio; nunca compartas el modelo.
Cadenas síncronas para todo. Cinco servicios en el camino de una petición multiplican la latencia y dividen la disponibilidad. Pasa trabajo a eventos siempre que el usuario no necesite esperar.
Sin disciplina de versionado. Renombrar un campo JSON rompe consumidores que ni siquiera conoces. Solo cambios aditivos, con periodos de deprecación explícitos.
Ignorar la factura de la plataforma. Decidirse por microservicios sin financiar pipelines, trazas y guardias produce servicios que nadie sabe operar y que todos temen desplegar.
Ideas clave
- La propiedad que define a los microservicios es el despliegue independiente; si los servicios tienen que entregarse juntos, la distribución no aporta nada y cuesta mucho.
- Traza los límites alrededor de las capacidades de negocio y valídalos contando cuántos servicios toca una funcionalidad típica.
- Cada servicio es dueño de sus datos en privado; las consultas entre servicios se resuelven con composición, modelos de lectura replicados o un almacén analítico separado, nunca con un esquema compartido.
- Usa llamadas síncronas cuando el llamante necesita una respuesta y eventos para los cambios de estado que se propagan, y trata el versionado de interfaces como una preocupación de diseño de primer nivel.
- La inversión operativa (un pipeline por servicio, trazas, métricas, pruebas de contrato, guardias) es el coste real y debe financiarse antes de la primera división.
- Un monolito modular ofrece la mayor parte del beneficio de diseño sin la complejidad distribuida, y es el mejor punto de partida para una futura extracción mediante strangler fig.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Qué tamaño debe tener un microservicio?
- El tamaño es la medida equivocada. Un servicio debe encargarse por completo de una capacidad de negocio, datos incluidos, y ser lo bastante pequeño como para que el equipo responsable pueda reescribirlo en unas semanas si hace falta. Contar líneas de código o imponer un número máximo de ficheros produce servicios que no pueden responder a una pregunta sin llamar a cuatro vecinos, algo mucho peor que un servicio que simplemente resulta ser grande.
- ¿Pueden varios microservicios compartir una misma base de datos?
- Técnicamente sí, y el resultado es un monolito distribuido. Cualquier cambio de esquema se convierte en una entrega coordinada, la consulta lenta de un servicio deja sin recursos a otro y nadie sabe ya quién escribe qué. Si dos servicios necesitan de verdad la misma tabla, es una señal clara de que son un solo servicio. Comparte los datos mediante interfaces publicadas o modelos de lectura replicados.
- ¿Los microservicios hacen más rápido un sistema?
- Casi nunca a nivel de una petición individual. Convertir una llamada a un método dentro del proceso en una llamada de red añade serialización, transporte y gestión de fallos, normalmente un milisegundo o más cada vez. Lo que aportan los microservicios es escalar componentes de forma independiente, de modo que los tres endpoints que más CPU consumen reciban más capacidad sin pagar por toda la aplicación, y velocidad de entrega para equipos que ya no hacen cola unos detrás de otros.
- ¿Es un monolito modular un paso atrás?
- A menudo es el destino correcto. Un único desplegable con límites entre módulos aplicados de forma estricta, un esquema por módulo y nada de SQL entre módulos ofrece casi todo el beneficio de diseño sin ninguno de los costes de la distribución. Además, deja la puerta abierta: los módulos con límites limpios pueden extraerse más adelante cuando una presión concreta (escalado independiente, un ritmo de entregas distinto, otra tecnología) lo justifique.