# Día 28: Arquitecturas Distribuidas: Sagas, Circuit Breaker, Resiliencia y Librerías Spring Boot

> Patrón Saga (Orquestación vs Coreografía), Resilience4j, Spring Boot Actuator y Starters Esenciales

- Plan: [JavaMastery](https://aprende-java.webs-economicas.es/), día 28 de 30
- Fase 7: API Gateway, API-First, Arquitectura Hexagonal Enterprise y Sagas
- Dedicación: 4.5h
- Profundidad: visión de conjunto
- Revisado: 2026-10-29
- Versión interactiva: https://aprende-java.webs-economicas.es/dias/sagas-circuit-breaker-resiliencia/

## Al terminar este día sabrás

- 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.


## Conceptos

### 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.

**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.

*Pasos y compensaciones de una saga de pedido*

```text
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.
```

- **En una línea:** `Saga: CrearPedido -> ReservarStock -> ProcesarPago. Si ProcesarPago falla -> LiberarStock -> CancelarPedido.`
- **Por qué importa:** Reemplaza las transacciones distribuidas (2PC/XA) que son frágiles y lentas en sistemas distribuidos, por una coordinación eventual basada en eventos.
- **Error típico:** Intentar usar `@Transactional` de Spring para abarcar múltiples bases de datos de microservicios distintos.
- **Java 17 frente a 21:** Patrón independiente de versión.
- **Se conecta con:** Compensación, Consistencia Eventual, Orquestació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.

**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.

*Orquestador sencillo como máquina de estados*

```java
@Component
class SagaPedidoOrquestador {

    @Transactional
    @KafkaListener(topics = "saga.pedidos.respuestas", groupId = "orquestador")
    void alResponder(RespuestaPaso r) {
        SagaPedido saga = sagas.findById(r.pedidoId()).orElseThrow();

        switch (saga.estado()) {
            case RESERVANDO_STOCK -> {
                if (r.ok()) { saga.pasarA(COBRANDO); comandos.enviar(new Cobrar(saga.pedidoId(), saga.importe())); }
                else        { saga.pasarA(CANCELANDO); comandos.enviar(new CancelarPedido(saga.pedidoId())); }
            }
            case COBRANDO -> {
                if (r.ok()) { saga.pasarA(RESERVANDO_TRANSPORTE); comandos.enviar(new ReservarTransporte(saga.pedidoId())); }
                else        { saga.pasarA(COMPENSANDO_STOCK); comandos.enviar(new LiberarStock(saga.pedidoId())); }
            }
            case RESERVANDO_TRANSPORTE -> {
                if (r.ok()) { saga.pasarA(COMPLETADA); comandos.enviar(new ConfirmarPedido(saga.pedidoId())); }
                else        { saga.pasarA(COMPENSANDO_PAGO); comandos.enviar(new Reembolsar(saga.pedidoId())); }
            }
            case COMPENSANDO_PAGO -> { saga.pasarA(COMPENSANDO_STOCK); comandos.enviar(new LiberarStock(saga.pedidoId())); }
            case COMPENSANDO_STOCK -> { saga.pasarA(CANCELANDO); comandos.enviar(new CancelarPedido(saga.pedidoId())); }
            default -> log.warn("Respuesta inesperada {} en estado {}", r, saga.estado());
        }
        // comandos.enviar() escribe en el outbox: estado y comando se guardan juntos
    }
}
```

- **En una línea:** `Coreografía: PedidoService publica PedidoCreado -> InventarioService escucha y publica StockReservado -> PagoService escucha y publica PagoConfirmado.`
- **Por qué importa:** 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.
- **Error típico:** Usar coreografía para sagas con más de 4-5 pasos, donde el flujo se vuelve imposible de seguir sin un diagrama.
- **Java 17 frente a 21:** Independiente de versión.
- **Se conecta con:** Kafka Choreography, Temporal.io, Axon 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.

**Cómo funciona por dentro.**

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.

*Resilience4j en Spring Boot*

```java
@Service
class InventarioClient {
    private final RestClient http;
    InventarioClient(RestClient.Builder b) { this.http = b.baseUrl("http://inventario").build(); }

    @CircuitBreaker(name = "inventario", fallbackMethod = "stockDesconocido")
    @Retry(name = "inventario")
    public Stock consultar(String sku) {
        return http.get().uri("/stock/{sku}", sku).retrieve().body(Stock.class);
    }

    private Stock stockDesconocido(String sku, Throwable causa) {
        return Stock.desconocido(sku);     // la tienda muestra "consultar disponibilidad"
    }
}

// application.yml
// resilience4j:
//   circuitbreaker:
//     instances:
//       inventario:
//         sliding-window-size: 20
//         failure-rate-threshold: 50            # % de fallos para abrir
//         slow-call-duration-threshold: 2s
//         slow-call-rate-threshold: 80
//         wait-duration-in-open-state: 30s
//         permitted-number-of-calls-in-half-open-state: 3
//   retry:
//     instances:
//       inventario:
//         max-attempts: 3
//         wait-duration: 200ms
//         enable-exponential-backoff: true
//         retry-exceptions: [java.io.IOException, org.springframework.web.client.ResourceAccessException]
```

- **En una línea:** `@CircuitBreaker(name = "inventario", fallbackMethod = "inventarioFallback") public StockDto consultarStock(Long productoId) { ... }`
- **Por qué importa:** Previene el efecto cascada: un servicio caído no arrastra al resto del sistema al saturar sus hilos con peticiones que siempre fallan.
- **Error típico:** No configurar un fallback (respuesta alternativa) cuando el circuito está abierto, devolviendo un error críptico al usuario.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Fallback, Bulkhead, Timeout

### 4. Librerías y Starters Esenciales del Ecosistema Spring Boot

Starters imprescindibles: `spring-boot-starter-web` (Spring MVC + Tomcat), `spring-boot-starter-data-jpa` (Hibernate + JPA), `spring-boot-starter-validation` (Jakarta Bean Validation), `spring-boot-starter-security` (Spring Security), `spring-boot-starter-actuator` (endpoints de salud/métricas), `spring-boot-starter-test` (JUnit 5 + Mockito + AssertJ). Librerías complementarias: `Resilience4j` (circuit breaker), `MapStruct` (mapping DTO<->Entity), `springdoc-openapi` (Swagger UI), `Flyway/Liquibase` (migraciones de BD), `Testcontainers` (tests con Docker).

**Cómo funciona por dentro.**

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.
- Seguridad: `starter-security`, `starter-oauth2-resource-server`.
- Mensajería: `spring-kafka`, `starter-amqp`.
- Operación: `starter-actuator`, `micrometer-registry-prometheus`, `micrometer-tracing-bridge-otel`.
- Resiliencia: `resilience4j-spring-boot3`.
- 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.

*Dependencias de un microservicio típico*

```xml
<dependencies>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>

  <dependency><groupId>org.flywaydb</groupId><artifactId>flyway-database-postgresql</artifactId></dependency>
  <dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><scope>runtime</scope></dependency>
  <dependency><groupId>io.micrometer</groupId><artifactId>micrometer-registry-prometheus</artifactId></dependency>
  <dependency><groupId>io.micrometer</groupId><artifactId>micrometer-tracing-bridge-otel</artifactId></dependency>
  <dependency><groupId>io.opentelemetry</groupId><artifactId>opentelemetry-exporter-otlp</artifactId></dependency>
  <dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot3</artifactId><version>2.2.0</version></dependency>

  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-testcontainers</artifactId><scope>test</scope></dependency>
  <dependency><groupId>org.testcontainers</groupId><artifactId>postgresql</artifactId><scope>test</scope></dependency>
</dependencies>
```

- **En una línea:** `spring-boot-starter-web + spring-boot-starter-data-jpa + spring-boot-starter-validation + spring-boot-starter-actuator`
- **Por qué importa:** Conocer el ecosistema evita reinventar la rueda y permite componer soluciones robustas.
- **Error típico:** Añadir librerías duplicadas que hacen lo mismo (e.g. Jackson + Gson, JUnit 4 + JUnit 5).
- **Java 17 frente a 21:** Idéntico en 17 y 21 (verificar compatibilidad de versiones).
- **Se conecta con:** Maven Dependencies, Auto-Configuration, Spring 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).

**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.

*Actuator expuesto con criterio*

```yaml
management:
  server:
    port: 8081                         # solo accesible dentro del clúster
  endpoints:
    web:
      exposure:
        include: health, info, prometheus, loggers
  endpoint:
    health:
      probes:
        enabled: true                  # /actuator/health/liveness y /readiness
      show-details: when-authorized
      group:
        readiness:
          include: readinessState, db, kafka
  info:
    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"}'
```

- **En una línea:** `management.endpoints.web.exposure.include=health,info,prometheus,metrics
management.endpoint.health.probes.enabled=true`
- **Por qué importa:** Es el puente entre tu aplicación Java y las herramientas de observabilidad (Prometheus, Grafana, Kubernetes probes).
- **Error típico:** Exponer todos los endpoints de Actuator sin restricciones de seguridad en producción, filtrando información sensible.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Prometheus, Grafana, Kubernetes Probes, Micrometer


## Sesión de 90 minutos

### Bloque 1: Patrón Saga y Transacciones Distribuidas (0 - 25 min)

- **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?
- **Entregable:** 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?
- **Entregable:** 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?
- **Entregable:** 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?
- **Entregable:** Notas consolidadas con el stack de librerías recomendado.


## Explícalo desde cero

### Por qué las transacciones distribuidas (2PC/XA) son inadecuadas para microservicios

- **Qué 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.
- **Qué suele explicarse mal:** Pensar que se necesita una "super-transacción" que abarque todos los microservicios. Las Sagas aceptan la consistencia eventual.
- **Ejemplo sencillo:** 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).
- **Señal de alerta:** Si intentas usar `@Transactional` de Spring para coordinar operaciones en bases de datos de microservicios distintos.


## Preguntas

### Nivel 1 (Básico): ¿Qué es un fallback en un Circuit Breaker y por qué es importante?

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.

**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».

### Nivel 2 (Intermedio): ¿Cuándo es mejor una Saga por coreografía y cuándo por orquestación?

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.

**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.

### 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?

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.

**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.


## Reto del tutor

**Reto:** 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 está bien:** Señala que el sleep bloquea el hilo durante 30 segundos.
- **Respuesta superficial:** Decir que use un try-catch.
- **Lo que falta de fondo:** 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).
- **Respuesta senior:** 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.


## Katas

### Kata 28.1: Circuit Breaker con Resilience4j y Fallback

Crea un servicio que llame a un endpoint externo simulado. Configura un @CircuitBreaker con failureRateThreshold=50 y un método fallback que devuelva datos cacheados. Simula fallos y verifica que el circuito se abre tras superar el umbral.


## 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.


## Proyecto guiado «Pedidos»: Saga del pedido y resiliencia

Coordinar reserva de stock, cobro y envío entre servicios, deshaciendo lo hecho si algo falla.

**Tareas:**

- 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.


## Fuentes oficiales

- [Patrón Saga](https://microservices.io/patterns/data/saga.html)
- [Martin Fowler: Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html)
- [Resilience4j: CircuitBreaker](https://resilience4j.readme.io/docs/circuitbreaker)
- [Spring Cloud Circuit Breaker: referencia](https://docs.spring.io/spring-cloud-circuitbreaker/reference/)

