Microservicios en Java: cuándo dividir y qué hay que operar después
La parte fácil de los microservicios es partir el código. La difícil es todo lo que aparece después: consistencia entre servicios, trazabilidad, despliegues coordinados y fallos parciales.
Dividir resuelve un problema de organización, no de código
El coste de los microservicios es permanente. Conviene conocerlo antes de asumirlo, y comprobar que compra algo concreto.
Conviene dividir cuando el límite que estorba es organizativo: varios equipos que se bloquean entre sí en el mismo repositorio, releases que no pueden desacoplarse o componentes con necesidades de escalado radicalmente distintas.
Si un único equipo mantiene todo el sistema, dividirlo añade coordinación entre despliegues sin eliminar ninguna dependencia real: las dependencias siguen ahí, solo que ahora viajan por la red.
El precio que se paga incluye trazabilidad distribuida, consistencia eventual, versionado de contratos, gestión de fallos parciales, un entorno local que deja de ser trivial y una plataforma de despliegue que alguien tiene que operar.
El tamaño lo determina el dominio, no las líneas de código
Un servicio debe poder desplegarse, escalarse y fallar de forma independiente, y eso solo es posible si es dueño exclusivo de sus datos. El límite lo marca el bounded context, no una regla sobre cuántas personas caben en un equipo.
La prueba definitiva es la propiedad del dato: si dos servicios escriben en la misma tabla, no son dos servicios. Son un servicio con dos despliegues y un acoplamiento invisible que aparecerá en la primera migración de esquema.
Cuando hay dudas sobre dónde trazar el límite, la opción con menos riesgo es mantener los módulos dentro del mismo despliegue hasta que el límite se haya demostrado estable. Fusionar dos módulos es un refactor de una tarde; fusionar dos servicios con sus bases de datos es un proyecto.
Síncrono solo cuando quien llama necesita la respuesta
El criterio para elegir entre HTTP y mensajería es el acoplamiento temporal, no la preferencia tecnológica.
Con una llamada HTTP, si el servicio de destino está caído el origen falla; con un evento publicado en Kafka, el consumidor lo procesará cuando vuelva. Esa diferencia determina si una incidencia afecta a un servicio o se propaga en cascada por toda la plataforma.
Consumidores idempotentes
Todo broker entrega mensajes duplicados en algún momento. Procesarlos dos veces no puede tener efectos distintos que procesarlos una.
Timeouts y circuit breakers
En toda llamada saliente. Sin ellos, un servicio lento agota el pool de conexiones de quien le llama y la caída se propaga.
Contratos compatibles
Un cambio incompatible en un evento obliga a desplegar productor y consumidores a la vez, que es justo la autonomía que se buscaba al dividir.
Consistencia sin transacciones distribuidas
Las transacciones que abarcan varios servicios no son viables en la práctica, así que la consistencia se resuelve con eventos y compensaciones.
El patrón habitual es la saga: una secuencia de transacciones locales donde cada paso publica un evento que dispara el siguiente, y cada paso define su acción de compensación por si algo falla más adelante. No hay rollback global; hay operaciones inversas explícitas.
Para que un servicio publique un evento y actualice su base de datos de forma atómica se usa el patrón outbox: el evento se escribe en una tabla dentro de la misma transacción y un proceso aparte lo publica. Sin outbox existe una ventana en la que el dato se guardó y el evento nunca salió, y esa ventana genera inconsistencias silenciosas que aparecen semanas después.
Sin observabilidad no hay diagnóstico posible
En un sistema distribuido ningún servicio tiene por sí solo la información suficiente para explicar un fallo.
Trazas distribuidas
Con OpenTelemetry, propagando el contexto también a través del broker de mensajes y no solo en HTTP.
Métricas RED
Peticiones, errores y duración por servicio, expuestas con Micrometer y recogidas por Prometheus.
Logs estructurados
En JSON y con el traceId incluido, para saltar de una traza lenta a sus logs exactos.
Alertas por síntoma
Basadas en lo que percibe el usuario —latencia y tasa de error—, no en indicadores de recursos como el uso de CPU.
La implementación completa, con la configuración de cada herramienta, está en la guía de observabilidad en microservicios.
Preguntas frecuentes
Dudas habituales sobre java microservicios en proyectos empresariales.
¿Cuándo conviene dividir una aplicación Java en microservicios?
Conviene cuando el cuello de botella es organizativo: varios equipos que se bloquean entre sí en el mismo repositorio, releases que no pueden desacoplarse o componentes con necesidades de escalado muy distintas. Los microservicios resuelven un problema de autonomía, no de código: si un único equipo mantiene todo el sistema, dividirlo añade coordinación sin eliminar dependencias reales.
¿Cómo se decide el tamaño de un microservicio?
El tamaño lo determina el bounded context del dominio, no el número de líneas de código. Un servicio debe poder desplegarse, escalarse y fallar de forma independiente, lo que exige que sea dueño exclusivo de sus datos. La prueba definitiva es la propiedad del dato: si dos servicios escriben en la misma tabla, no son dos servicios sino uno con dos despliegues.
¿Cómo deben comunicarse los microservicios entre sí?
La comunicación síncrona por HTTP es adecuada solo cuando quien llama necesita la respuesta para continuar; para el resto, la mensajería asíncrona con eventos de dominio produce mucho menos acoplamiento. La diferencia clave es el acoplamiento temporal: con HTTP, un servicio caído hace fallar al que llama; con un evento, el consumidor lo procesará cuando se recupere.
¿Cómo se mantiene la consistencia de datos entre microservicios?
Mediante consistencia eventual y patrones de compensación, ya que las transacciones distribuidas entre servicios no son viables en la práctica. El patrón habitual es la saga: una secuencia de transacciones locales donde cada paso publica un evento y define su acción de compensación. Para publicar el evento y actualizar la base de datos de forma atómica se usa el patrón outbox.
¿Qué observabilidad necesita una arquitectura de microservicios?
Necesita métricas, logs y trazas correlacionados por un identificador común. Como mínimo: trazas distribuidas con OpenTelemetry propagando el contexto también a través del broker de mensajes, métricas RED por servicio con Micrometer y Prometheus, logs estructurados en JSON que incluyan el traceId, y alertas basadas en síntomas percibidos por el usuario en lugar de en uso de recursos.
Recursos relacionados
Páginas pilar, guías y casos reales que amplían lo tratado aquí.
- Arquitectura Java La decisión previa: dividir o no dividir.
- Domain-Driven Design Cómo trazar los límites antes de partir el sistema.
- Guía de observabilidad Métricas, logs y trazas con OpenTelemetry.
- Java Cloud Dónde se despliegan y cómo se operan.
- Cloud & DevOps CI/CD, Kubernetes y automatización.
- Casos reales Migraciones a microservicios en producción.
¿Estás valorando migrar a microservicios?
Analizo si la división está justificada en tu caso, dónde deben ir los límites y qué plataforma hace falta para operarla.