🛡️ Resilience4j Cheatsheet Completo 🛡️

Resilience4j es una librería de resiliencia ligera y modular para Java. Proporciona patrones de tolerancia a fallos como Circuit Breaker, Rate Limiter, Retry, Bulkhead y Time Limiter para mejorar la fiabilidad y la estabilidad de los microservicios en un entorno distribuido.


1. 🌟 Conceptos Clave de Resiliencia


2. 🛠️ Configuración Inicial (Spring Boot)

  1. Añadir dependencias en pom.xml (Maven):
    • Necesitarás el starter de Spring Boot para Resilience4j y las dependencias de los módulos específicos de resiliencia que quieras usar.
    <dependencies>
        <parent>...</parent> &lt;!-- Tu parent de Spring Boot -->
    
        &lt;!-- Starter principal de Resilience4j para Spring Boot -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot3</artifactId> &lt;!-- Para Spring Boot 3+ -->
            &lt;!-- Para Spring Boot 2.x, usa resilience4j-spring-boot2 -->
            <version>2.2.0</version> &lt;!-- Usar la versión más reciente -->
        </dependency>
        &lt;!-- Módulos individuales (se incluyen por defecto en spring-boot-starter, pero puedes ser explícito) -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-circuitbreaker</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-ratelimiter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-retry</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-bulkhead</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-timelimiter</artifactId>
            <version>2.2.0</version>
        </dependency>
    
        &lt;!-- Para métricas (Actuator y Prometheus/Grafana) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

3. 📝 Configuración en application.yml (o .properties)

Resilience4j se configura principalmente en los archivos de propiedades, definiendo instancias para cada patrón.

# src/main/resources/application.yml
resilience4j:
  circuitbreaker: # Configuración de Circuit Breaker
    instances:
      myProductService: # Nombre de la instancia del Circuit Breaker
        registerHealthIndicator: true # Registrar en /actuator/health
        failureRateThreshold: 50 # Porcentaje de fallos para abrir el circuito (default: 50)
        minimumNumberOfCalls: 5 # Mínimo de llamadas en el período deslizante para calcular la tasa de fallos
        slidingWindowType: COUNT_BASED # Tipo de ventana deslizante (COUNT_BASED o TIME_BASED)
        slidingWindowSize: 10 # Tamaño de la ventana deslizante (10 llamadas o 10 segundos)
        waitDurationInOpenState: 5s # Duración de espera en estado OPEN (default: 60s)
        permittedNumberOfCallsInHalfOpenState: 3 # Llamadas permitidas en estado HALF_OPEN
        automaticTransitionFromOpenToHalfOpenEnabled: true # Transición automática a HALF_OPEN
        recordExceptions:
          - org.springframework.web.client.ResourceAccessException # Registrar esta excepción como fallo
        ignoreExceptions:
          - com.example.myapp.exceptions.NotFoundException # Ignorar esta excepción (no la cuenta como fallo)
      myPaymentService:
        baseConfig: default # Hereda de la configuración 'default'
        failureRateThreshold: 70

  ratelimiter: # Configuración de Rate Limiter
    instances:
      myExternalApi: # Nombre de la instancia
        limitForPeriod: 10 # Número de llamadas permitidas por período
        limitRefreshPeriod: 1s # Duración del período para el límite (default: 500ms)
        timeoutDuration: 0s # Tiempo de espera para adquirir un permiso (0s = no esperar)
      anotherApi:
        limitForPeriod: 5
        limitRefreshPeriod: 5s

  retry: # Configuración de Retry
    instances:
      myDbOperation: # Nombre de la instancia
        maxAttempts: 3 # Número máximo de reintentos
        waitDuration: 1s # Duración de espera entre reintentos
        retryExceptions:
          - java.io.IOException
          - java.sql.SQLException
        ignoreExceptions:
          - com.example.myapp.exceptions.ValidationException
      paymentRetry:
        maxAttempts: 5
        waitDuration: 500ms
        exponentialBackoffMultiplier: 2 # Duplica el tiempo de espera en cada reintento

  bulkhead: # Configuración de Bulkhead (Thread Pool Bulkhead)
    instances:
      myHeavyService: # Nombre de la instancia
        maxWaitDuration: 0 # Tiempo de espera para adquirir un permiso (0ms = no esperar)
        maxConcurrentCalls: 5 # Número máximo de llamadas concurrentes (Fixed-Size Bulkhead)
    threadPoolBulkhead: # Bulkhead con Thread Pool dedicado
      instances:
        myThreadPoolHeavyService: # Nombre de la instancia del Thread Pool Bulkhead
          maxThreadPoolSize: 10 # Tamaño máximo del pool de hilos
          coreThreadPoolSize: 5 # Hilos core
          queueCapacity: 20 # Capacidad de la cola

  timelimiter: # Configuración de Time Limiter
    instances:
      longRunningTask: # Nombre de la instancia
        timeoutDuration: 2s # Duración de timeout para la llamada
        cancelRunningFuture: true # Cancelar la tarea si excede el tiempo

# Configuración de Actuator para exponer métricas de Resilience4j
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus # Exponer health, info y prometheus
  endpoint:
    health:
      show-details: always
    metrics:
      export:
        prometheus:
          enabled: true

4. 🚀 Uso de Patrones en Código Java

4.1. Con Anotaciones (Recomendado para Spring Boot)

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture; // Para TimeLimiter asíncrono
import java.util.concurrent.CompletionStage; // Para TimeLimiter asíncrono

// Excepción personalizada de ejemplo
class ExternalServiceException extends RuntimeException {
    public ExternalServiceException(String msg) { super(msg); }
}

@Service
public class ExternalApiService {

    private int callCount = 0;

    // Circuit Breaker
    @CircuitBreaker(name = "myProductService", fallbackMethod = "getProductsFallback")
    public String getProductsFromExternalService() {
        callCount++;
        System.out.println("Llamada al servicio externo de productos. Intento #" + callCount);
        if (callCount % 3 != 0) { // Simula un fallo en 2 de cada 3 llamadas
            throw new ExternalServiceException("Error simulado del servicio externo");
        }
        return "Lista de productos obtenida";
    }

    private String getProductsFallback(Throwable t) { // Método fallback debe tener la misma firma o aceptar un Throwable
        System.err.println("Fallback para getProductsFromExternalService: " + t.getMessage());
        return "Productos por defecto / Cache";
    }

    // Rate Limiter
    @RateLimiter(name = "myExternalApi", fallbackMethod = "rateLimitFallback")
    public String callExternalApi() {
        System.out.println("Llamada permitida por Rate Limiter");
        return "Respuesta de API";
    }

    private String rateLimitFallback(Throwable t) {
        System.err.println("Fallback por límite de tasa: " + t.getMessage());
        return "Límite de tasa excedido";
    }

    // Retry
    @Retry(name = "myDbOperation", fallbackMethod = "dbOperationFallback")
    public String performDbOperation() throws IOException {
        System.out.println("Intentando operación de base de datos...");
        if (Math.random() > 0.5) { // Simula un fallo transitorio 50% de las veces
            throw new IOException("Fallo de conexión a DB simulado");
        }
        return "Operación DB exitosa";
    }

    private String dbOperationFallback(IOException e) {
        System.err.println("Fallback para operación DB después de reintentos: " + e.getMessage());
        return "Datos de DB en caché";
    }

    // Bulkhead (Thread Pool Bulkhead para operaciones asíncronas)
    // Para Thread Pool Bulkhead, el método debe devolver CompletableFuture o CompletionStage
    @Bulkhead(name = "myThreadPoolHeavyService", type = Bulkhead.Type.THREADPOOL, fallbackMethod = "heavyServiceFallback")
    public CompletionStage<String> callHeavyServiceAsync() {
        return CompletableFuture.supplyAsync(() -&gt; {
            System.out.println("Ejecutando tarea pesada en pool de hilos...");
            try {
                Thread.sleep(2000); // Tarea que tarda 2 segundos
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            return "Resultado de tarea pesada";
        });
    }

    private CompletionStage<String> heavyServiceFallback(Throwable t) {
        System.err.println("Fallback por Bulkhead/timeout en tarea pesada: " + t.getMessage());
        return CompletableFuture.completedFuture("Resultado por defecto de tarea pesada");
    }

    // Time Limiter (con Circuit Breaker y Retry)
    @CircuitBreaker(name = "longRunningTask", fallbackMethod = "timeLimitAndCircuitBreakerFallback")
    @Retry(name = "longRunningTask")
    @TimeLimiter(name = "longRunningTask", fallbackMethod = "timeLimitAndCircuitBreakerFallback")
    public CompletionStage<String> callLongRunningTask() {
        return CompletableFuture.supplyAsync(() -&gt; {
            System.out.println("Iniciando tarea de larga duración...");
            try {
                Thread.sleep(3000); // Simula una tarea que excede el timeout de 2s
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            return "Tarea de larga duración completada";
        });
    }

    private CompletionStage<String> timeLimitAndCircuitBreakerFallback(Throwable t) {
        System.err.println("Fallback para tarea de larga duración: " + t.getMessage());
        return CompletableFuture.completedFuture("Resultado de timeout");
    }
}

4.2. Con Decoradores Funcionales (Programáticamente)

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry;
import io.vavr.CheckedFunction0; // Para funciones que lanzan excepciones

public class FunctionalDecoratorsExample {

    private final CircuitBreaker circuitBreaker;
    private final Retry retry;

    public FunctionalDecoratorsExample(CircuitBreakerRegistry cbRegistry, RetryRegistry retryRegistry) {
        this.circuitBreaker = cbRegistry.circuitBreaker("myProductService");
        this.retry = retryRegistry.retry("myDbOperation");
    }

    public String callProductService() {
        CheckedFunction0<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(
            circuitBreaker,
            () -&gt; {
                // Lógica original del servicio
                System.out.println("Calling product service via functional decorator.");
                if (Math.random() &lt; 0.5) { throw new ExternalServiceException("Simulated failure."); }
                return "Product list (functional)";
            });

        try {
            return decoratedSupplier.apply(); // Ejecuta la función decorada
        } catch (Throwable e) {
            System.err.println("Functional CB Fallback: " + e.getMessage());
            return "Fallback product list (functional)";
        }
    }

    public String callDbOperationWithRetry() {
        CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(
            retry,
            () -&gt; {
                System.out.println("Calling DB operation via functional decorator.");
                if (Math.random() &lt; 0.8) { throw new IOException("Simulated DB transient failure."); }
                return "DB operation success (functional)";
            });
        try {
            return retryableSupplier.apply();
        } catch (Throwable e) {
            System.err.println("Functional Retry Fallback: " + e.getMessage());
            return "DB operation fallback (functional)";
        }
    }
}

5. 📊 Métricas y Monitoreo


6. 💡 Buenas Prácticas y Consejos


Este cheatsheet te proporciona una referencia completa de Resilience4j, cubriendo sus conceptos esenciales de resiliencia, cómo configurarlo en Spring Boot, el uso de sus anotaciones en código Java, las métricas clave y las mejores prácticas para construir microservicios robustos y tolerantes a fallos.