Coordinar una operación que abarca varios servicios con una saga y sus compensaciones.
Elegir entre coreografía y orquestación según el tamaño del flujo.
Proteger las llamadas remotas con circuit breaker, timeout, reintentos y bulkhead.
Exponer salud y métricas con Actuator de forma segura.
1
El Patrón Saga: Transacciones Distribuidas sin 2PC
En un sistema de microservicios, una operación de negocio (e.g. "crear pedido") puede involucrar múltiples servicios (pedidos, inventario, pagos, envíos). Una Saga coordina esta operación como una secuencia de transacciones locales donde cada servicio confirma su parte y publica un evento. Si un paso falla, se ejecutan acciones compensatorias (rollback lógico) en los servicios previos.
En una líneaSaga: CrearPedido -> ReservarStock -> ProcesarPago. Si ProcesarPago falla -> LiberarStock -> CancelarPedido.
Pasos y compensaciones de una saga de pedidotext
Paso Servicio Acción Compensación
1. Crear pedido pedidos estado = PENDIENTE estado = CANCELADO
2. Reservar stock inventario reservar(pedido, líneas) liberar(pedido)
3. Cobrar pagos cobrar(pedido, importe) reembolsar(pedido)
4. Reservar transporte envíos reservar(pedido, dirección) cancelarReserva(pedido)
5. Confirmar pedido pedidos estado = CONFIRMADO (paso final, no se compensa)
Si falla el paso 4:
reembolsar(pedido) -> liberar(pedido) -> pedido CANCELADO
Cada compensación usa el id del pedido como clave de idempotencia.
Cómo funciona por dentro
Cuando una operación de negocio toca varios servicios, cada uno con su propia base de datos, no hay una transacción que los abarque. Una saga la descompone en una secuencia de transacciones locales, una por servicio. Cada paso confirma su parte y desencadena el siguiente. Si un paso falla, se ejecutan transacciones de compensación para deshacer semánticamente los pasos ya confirmados, en orden inverso.
Compensar no es hacer rollback: el cobro ya ocurrió y se ve en el extracto, así que la compensación es un reembolso. Por eso las compensaciones deben ser idempotentes (se pueden reintentar sin efectos dobles) y diseñadas desde el principio, paso a paso.
Las sagas no ofrecen aislamiento: otros procesos pueden ver estados intermedios. Se mitiga con estados explícitos (PENDIENTE_PAGO, RESERVADO) que el resto del sistema entiende, y ordenando los pasos para que los que no se pueden compensar (enviar un correo, emitir una factura legal) vayan al final.
Reemplaza las transacciones distribuidas (2PC/XA) que son frágiles y lentas en sistemas distribuidos, por una coordinación eventual basada en eventos.
Intentar usar @Transactional de Spring para abarcar múltiples bases de datos de microservicios distintos.
Patrón independiente de versión.
Se conecta conCompensaciónConsistencia EventualOrquestación vs Coreografía
2
Saga por Coreografía vs Saga por Orquestación
Coreografía: cada servicio reacciona autónomamente a los eventos de los demás sin un coordinador central (pub/sub puro). Orquestación: un servicio coordinador (Saga Orchestrator) dirige la secuencia, indicando a cada servicio qué hacer y gestionando las compensaciones si algo falla.
En una líneaCoreografía: PedidoService publica PedidoCreado -> InventarioService escucha y publica StockReservado -> PagoService escucha y publica PagoConfirmado.
Orquestador sencillo como máquina de estadosjava
@ComponentclassSagaPedidoOrquestador {
@Transactional@KafkaListener(topics = "saga.pedidos.respuestas", groupId = "orquestador")voidalResponder(RespuestaPaso r) {
SagaPedidosaga= sagas.findById(r.pedidoId()).orElseThrow();
switch (saga.estado()) {
case RESERVANDO_STOCK -> {
if (r.ok()) { saga.pasarA(COBRANDO); comandos.enviar(newCobrar(saga.pedidoId(), saga.importe())); }
else { saga.pasarA(CANCELANDO); comandos.enviar(newCancelarPedido(saga.pedidoId())); }
}
case COBRANDO -> {
if (r.ok()) { saga.pasarA(RESERVANDO_TRANSPORTE); comandos.enviar(newReservarTransporte(saga.pedidoId())); }
else { saga.pasarA(COMPENSANDO_STOCK); comandos.enviar(newLiberarStock(saga.pedidoId())); }
}
case RESERVANDO_TRANSPORTE -> {
if (r.ok()) { saga.pasarA(COMPLETADA); comandos.enviar(newConfirmarPedido(saga.pedidoId())); }
else { saga.pasarA(COMPENSANDO_PAGO); comandos.enviar(newReembolsar(saga.pedidoId())); }
}
case COMPENSANDO_PAGO -> { saga.pasarA(COMPENSANDO_STOCK); comandos.enviar(newLiberarStock(saga.pedidoId())); }
case COMPENSANDO_STOCK -> { saga.pasarA(CANCELANDO); comandos.enviar(newCancelarPedido(saga.pedidoId())); }
default -> log.warn("Respuesta inesperada {} en estado {}", r, saga.estado());
}
// comandos.enviar() escribe en el outbox: estado y comando se guardan juntos
}
}
Cómo funciona por dentro
En la coreografía no hay coordinador: cada servicio escucha eventos y publica los suyos. Pedidos publica PedidoCreado, inventario reacciona y publica StockReservado, pagos reacciona a ese evento, y así sucesivamente. Es simple con pocos pasos y mantiene los servicios muy desacoplados, pero el flujo completo no está escrito en ningún sitio: hay que reconstruirlo leyendo los listeners de cada servicio, y con muchos pasos se vuelve difícil de seguir y de cambiar.
En la orquestación, un componente (el orquestador) conoce el flujo completo, envía comandos a cada servicio y decide el siguiente paso o la compensación según las respuestas. Guarda el estado de cada saga, así que sabes en qué punto está cada pedido. Puede ser una máquina de estados propia o un motor como Temporal o Camunda.
Regla orientativa: coreografía para flujos cortos (dos o tres pasos) y estables; orquestación cuando hay muchos pasos, ramas condicionales, tiempos de espera o necesidad de visibilidad del estado.
Coreografía es más simple pero se vuelve difícil de rastrear con muchos servicios (spaghetti de eventos). Orquestación centraliza el flujo y facilita el debugging, pero introduce un punto de coordinación.
Usar coreografía para sagas con más de 4-5 pasos, donde el flujo se vuelve imposible de seguir sin un diagrama.
Independiente de versión.
Se conecta conKafka ChoreographyTemporal.ioAxon Framework
3
Circuit Breaker con Resilience4j en Spring Boot
Cuando un servicio downstream falla repetidamente (timeout, 500), el Circuit Breaker abre el circuito para evitar enviar más peticiones al servicio caído. En estado CLOSED las peticiones pasan normalmente. Si el porcentaje de fallos supera un umbral, pasa a OPEN y rechaza inmediatamente las peticiones (fail-fast). Después de un tiempo, pasa a HALF_OPEN para probar si el servicio se ha recuperado.
En una línea@CircuitBreaker(name = "inventario", fallbackMethod = "inventarioFallback") public StockDto consultarStock(Long productoId) { ... }
Un circuit breaker protege a tu servicio de una dependencia que está fallando. En estado cerrado deja pasar las llamadas y mide el porcentaje de fallos y de llamadas lentas en una ventana deslizante. Si supera el umbral, se abre: durante un tiempo rechaza las llamadas de inmediato (sin esperar timeouts) y ejecuta un fallback. Pasado ese tiempo, pasa a semiabierto y deja pasar unas pocas llamadas de prueba; si van bien se cierra, si fallan se vuelve a abrir.
Evita dos cosas: malgastar hilos y conexiones esperando a algo que no va a responder, y seguir golpeando a un servicio que intenta recuperarse. Resilience4j lo combina con otros patrones: TimeLimiter (no esperar indefinidamente), Retry (reintentar fallos transitorios con backoff), Bulkhead (limitar llamadas concurrentes) y RateLimiter.
Con anotaciones, el orden de aplicación es Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( función ) ) ) ) ): los reintentos envuelven al breaker, así que cada reintento cuenta para sus estadísticas.
Previene el efecto cascada: un servicio caído no arrastra al resto del sistema al saturar sus hilos con peticiones que siempre fallan.
No configurar un fallback (respuesta alternativa) cuando el circuito está abierto, devolviendo un error críptico al usuario.
Idéntico en 17 y 21.
Se conecta conFallbackBulkheadTimeout
4
Librerías y Starters Esenciales del Ecosistema Spring Boot
Los starters de Spring Boot son dependencias que agrupan una funcionalidad con sus librerías compatibles y su autoconfiguración. Conocer los esenciales te permite montar un servicio profesional sin buscar versiones a mano.
Web y API: starter-web (MVC + Tomcat + Jackson), starter-validation, springdoc-openapi.
Datos: starter-data-jpa, starter-jdbc, flyway-core con flyway-database-postgresql, starter-cache con Caffeine o Redis.
Desarrollo y test: starter-test, spring-boot-testcontainers, spring-boot-docker-compose, spring-boot-devtools y spring-boot-configuration-processor (autocompletado de tus propiedades).
Fuera del ecosistema oficial son habituales MapStruct (mapeo generado en compilación) y ArchUnit. Lombok es popular, pero con records ha perdido gran parte de su sentido.
Conocer el ecosistema evita reinventar la rueda y permite componer soluciones robustas.
Añadir librerías duplicadas que hacen lo mismo (e.g. Jackson + Gson, JUnit 4 + JUnit 5).
Idéntico en 17 y 21 (verificar compatibilidad de versiones).
Se conecta conMaven DependenciesAuto-ConfigurationSpring Boot BOM
5
Spring Boot Actuator: Salud, Métricas y Endpoints de Gestión
Actuator expone endpoints HTTP para monitorizar la aplicación en producción: /actuator/health (estado general, liveness y readiness para Kubernetes), /actuator/info (metadatos de la aplicación), /actuator/metrics (métricas de JVM, conexiones de pool, latencias HTTP), /actuator/env (variables de entorno), /actuator/prometheus (métricas en formato Prometheus para Grafana).
En una líneamanagement.endpoints.web.exposure.include=health,info,prometheus,metrics
management.endpoint.health.probes.enabled=true
Actuator expuesto con criterioyaml
management:server:port:8081# solo accesible dentro del clústerendpoints:web:exposure:include:health,info,prometheus,loggersendpoint:health:probes:enabled:true# /actuator/health/liveness y /readinessshow-details:when-authorizedgroup:readiness:include:readinessState,db,kafkainfo:git:mode:simple# rama y commit si usas git-commit-id-maven-plugin# Cambiar el nivel de log en caliente:# curl -X POST localhost:8081/actuator/loggers/com.acme.taskflow \# -H 'Content-Type: application/json' -d '{"configuredLevel":"DEBUG"}'
Cómo funciona por dentro
Actuator añade endpoints de operación bajo /actuator. Los más útiles: health (estado de la aplicación y de sus dependencias, con grupos liveness y readiness para Kubernetes), info (versión, commit de Git), metrics y prometheus (métricas), loggers (consultar y cambiar el nivel de log en caliente, sin reiniciar) y threaddump/heapdump para diagnóstico.
Por defecto solo health se expone por HTTP. Expón explícitamente lo que necesites y nada más: env, configprops o heapdump pueden revelar secretos o datos personales. Una práctica común es servir Actuator en un puerto de gestión separado (management.server.port) que no se publica fuera del clúster.
Puedes añadir tus propios indicadores de salud implementando HealthIndicator, por ejemplo para comprobar que un proveedor externo responde. Ten cuidado con qué incluyes en cada grupo: una dependencia caída no debe hacer que Kubernetes reinicie tus pods.
Es el puente entre tu aplicación Java y las herramientas de observabilidad (Prometheus, Grafana, Kubernetes probes).
Exponer todos los endpoints de Actuator sin restricciones de seguridad en producción, filtrando información sensible.
Idéntico en 17 y 21.
Se conecta conPrometheusGrafanaKubernetes ProbesMicrometer
Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.
Qué estudiar. Saga por coreografía vs orquestación, acciones compensatorias.
Pregunta a responder. ¿Cómo compensar un pago ya cobrado si el servicio de envíos falla?
Resultado esperado. Diagrama de una Saga de 4 pasos con compensaciones.
Bloque 2: Circuit Breaker y Resilience4j (25 - 50 min)
Qué estudiar. Configuración de @CircuitBreaker, estados CLOSED/OPEN/HALF_OPEN, fallbacks.
Pregunta a responder. ¿Cómo evitar que un servicio caído colapse todo el sistema?
Resultado esperado. Servicio con Circuit Breaker y fallback funcional.
Bloque 3: Starters de Spring Boot y Actuator (50 - 80 min)
Qué estudiar. Listado de starters esenciales, endpoints de Actuator, configuración de health probes.
Pregunta a responder. ¿Qué endpoints de Actuator son seguros de exponer públicamente?
Resultado esperado. application.yml con Actuator configurado para Kubernetes readiness/liveness.
Cierre y Consolidación (80 - 90 min)
Qué estudiar. Repaso de patrones de resiliencia y checklist de librerías.
Pregunta a responder. ¿Qué 5 librerías no pueden faltar en cualquier proyecto Spring Boot de producción?
Resultado esperado. Notas consolidadas con el stack de librerías recomendado.
Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.
Explícalo desde cero
Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.
Por qué las transacciones distribuidas (2PC/XA) son inadecuadas para microservicios
Lo que debes entender
El protocolo 2PC (Two-Phase Commit) requiere que un coordinador central bloquee recursos en TODAS las bases de datos participantes hasta que todas confirmen. Si una base de datos es lenta o está caída, todas las demás quedan bloqueadas esperando. En un sistema de microservicios con decenas de servicios independientes, esto crea cuellos de botella masivos. Las Sagas reemplazan el bloqueo pesimista por compensaciones optimistas.
Analogía para contarlo
Un 2PC es como una carrera de relevos donde TODOS los corredores deben estar listos antes de que suene la pistola. Una Saga es como corredores independientes que corren y, si uno se cae, los anteriores desandan el camino (compensación).
Lo que suele explicarse mal
Pensar que se necesita una "super-transacción" que abarque todos los microservicios. Las Sagas aceptan la consistencia eventual.
Señal de que aún no lo dominas
Si intentas usar @Transactional de Spring para coordinar operaciones en bases de datos de microservicios distintos.
Preguntas trampa
Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.
Nivel 1 (Básico)¿Qué es un fallback en un Circuit Breaker y por qué es importante?Ver respuesta
Respuesta razonada
Un fallback es la respuesta alternativa que se devuelve cuando la llamada protegida falla o el circuito está abierto. Puede ser un valor por defecto, datos de una caché (aunque estén algo desactualizados), una respuesta degradada («precio no disponible») o encolar la operación para más tarde.
Es importante porque convierte el fallo de una dependencia en una degradación controlada en lugar de un error en cascada: la página de producto se sigue mostrando sin las recomendaciones, en vez de devolver un 500. Pero un fallback debe ser honesto: nunca puede inventar un resultado que el negocio interprete como real, como dar un pago por bueno cuando no se sabe. En esos casos el fallback correcto es un error claro o un estado «pendiente».
Qué está evaluando la pregunta
Un fallback es la respuesta alternativa que se devuelve cuando el circuito está abierto (servicio caído). Sin fallback, el usuario recibe un error críptico. Con fallback, puede recibir datos cacheados, un mensaje informativo o una respuesta parcial degradada que mantiene la funcionalidad mínima.
Nivel 2 (Intermedio)¿Cuándo es mejor una Saga por coreografía y cuándo por orquestación?Ver respuesta
Respuesta razonada
Coreografía cuando el flujo es corto (dos o tres pasos), estable y cada servicio puede reaccionar a eventos sin necesitar una visión global. Mantiene el acoplamiento mínimo y no introduce un componente central.
Orquestación cuando hay muchos pasos, lógica condicional, tiempos de espera, compensaciones complejas o necesidad de saber en todo momento en qué estado está cada operación (soporte, auditoría). El flujo queda escrito en un solo lugar, es más fácil de cambiar y de probar, a cambio de un componente más que mantener. En la práctica muchos sistemas combinan ambas: orquestación dentro de un proceso de negocio complejo y eventos coreografiados para notificar al resto de la organización.
Qué está evaluando la pregunta
Coreografía encaja en flujos simples de 2-3 servicios con eventos lineales. Orquestación encaja en flujos complejos de 4+ servicios con ramificaciones, condiciones y compensaciones sofisticadas donde el flujo debe ser visible y depurable desde un solo punto.
Nivel 3 (Entrevista Senior)¿Cómo implementarías la compensación en una Saga si el servicio de pagos confirma el cobro pero el servicio de envíos falla al reservar el transporte?Ver respuesta
Respuesta razonada
Al fallar la reserva de transporte, el orquestador (o el servicio de envíos con un evento TransporteNoDisponible) inicia las compensaciones en orden inverso: primero Reembolsar(pedido) en pagos, después LiberarStock(pedido) en inventario y por último marcar el pedido como CANCELADO, notificando al cliente. Antes de compensar puede tener sentido reintentar la reserva o probar otro transportista, si el negocio lo permite.
Las compensaciones deben ser idempotentes, usando el id del pedido como clave: si el reembolso se reintenta, la pasarela no debe devolver el dinero dos veces. También deben reintentarse hasta tener éxito, porque una compensación que falla deja el sistema inconsistente; si tras varios intentos sigue fallando, la saga pasa a un estado que requiere intervención manual y genera una alerta.
Qué está evaluando la pregunta
El orquestador de la Saga debe ejecutar la compensación en orden inverso: primero emitir un evento/comando ReembolsarPago al servicio de pagos para devolver el dinero, y luego CancelarPedido al servicio de pedidos. Cada compensación debe ser idempotente porque puede ejecutarse múltiples veces si el sistema de mensajería la reintenta.
Reto del tutor
Un equipo junior implementa un microservicio que llama a un servicio externo de verificación de identidad. Cuando el servicio externo está caído, su aplicación hace Thread.sleep(30000) y reintenta. Explica por qué este enfoque es desastroso y cómo lo rediseñarías con patrones de resiliencia.
Lo que una respuesta suele tener bien
Señala que el sleep bloquea el hilo durante 30 segundos.
Lo que suele quedarse en la superficie
Decir que use un try-catch.
El concepto de fondo que suele faltar
No advertir que cada petición concurrente bloqueará su hilo 30 segundos. Con 200 peticiones simultáneas, los 200 hilos del pool de Tomcat estarán congelados y la aplicación dejará de responder por completo (thread starvation).
Cómo lo respondería alguien senior en una entrevista
Es un antipatrón catastrófico: (1) Thread starvation: 200 peticiones simultáneas congelan 200 hilos de Tomcat durante 30s cada uno, dejando la app muerta; (2) El servicio externo caído recibe una avalancha de reintentos que empeoran su situación. Solución: (a) Circuit Breaker con Resilience4j que detecte el umbral de fallos y deje de enviar peticiones; (b) Retry con backoff exponencial (100ms, 200ms, 400ms) limitado a 3 intentos; (c) Timeout de 3 segundos máximo por petición; (d) Fallback que devuelva una respuesta degradada (verificación pendiente). En Java 21 con Virtual Threads, el bloqueo no agota los hilos del SO, pero sigue siendo ineficiente y saturará el servicio externo.
Proyecto «Pedidos» · paso 28 de 30
Saga del pedido y resiliencia
Coordinar reserva de stock, cobro y envío entre servicios, deshaciendo lo hecho si algo falla.
De qué va el proyecto
El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.
Qué hacer
Implementa la saga reserva de stock → cobro → envío, con una compensación para cada paso.
Protege la llamada a la pasarela de pago simulada con un circuit breaker de Resilience4j y un fallback.
Expón /actuator/health con los grupos de liveness y readiness.
Está listo cuando
Si el cobro falla, el stock reservado vuelve a estar disponible.
Con la pasarela caída, el circuit breaker se abre y los pedidos quedan pendientes de pago en lugar de fallar.
Katas: ejercicios cortos
Reglas de oro
Nunca uses @Transactional para coordinar operaciones en bases de datos de microservicios distintos; usa el patrón Saga.
Configura siempre Circuit Breaker + Retry + Timeout en llamadas a servicios externos.
Define un fallback para cada Circuit Breaker que devuelva una respuesta degradada pero funcional.
Protege los endpoints de Actuator con seguridad; solo expón health, info y prometheus en producción.
Diseña las acciones compensatorias de las Sagas para ser idempotentes.