Circuit Breaker Pattern in Spring Boot using Resilience4j

0

Introduction

The Circuit Breaker pattern is a design pattern used to detect failures and prevent cascading failures in distributed systems. It helps build resilient and fault-tolerant applications by temporarily disabling failing services to prevent system overload.

How Circuit Breaker Works

States

  1. CLOSED: Normal operation state
  • Requests pass through to the service
  • Failures are counted
  • Transitions to OPEN when failure threshold is reached
  1. OPEN: Failure state
  • Requests immediately fail without calling service
  • After wait duration, transitions to HALF_OPEN
  1. HALF_OPEN: Recovery testing state
  • Limited requests allowed through
  • Success leads to CLOSED state
  • Failure leads back to OPEN state

Flow Diagram

stateDiagram-v2
    [*] --> CLOSED
    CLOSED --> OPEN: Failure threshold reached
    OPEN --> HALF_OPEN: Wait duration expired
    HALF_OPEN --> CLOSED: Success threshold reached
    HALF_OPEN --> OPEN: Failure threshold reached

Implementation in Spring Boot

1. Dependencies

Add these to your pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

2. Configuration

Create application.yml:

resilience4j:
  circuitbreaker:
    instances:
      orderService:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true
        waitDurationInOpenState: 5s
        failureRateThreshold: 50
        eventConsumerBufferSize: 10
        recordExceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException

3. Service Implementation

@Service
public class OrderService {

    private final RestTemplate restTemplate;

    @Autowired
    public OrderService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @CircuitBreaker(name = "orderService", fallbackMethod = "fallbackForGetOrders")
    public List<Order> getOrders() {
        return restTemplate.getForObject("http://order-api/orders", List.class);
    }

    public List<Order> fallbackForGetOrders(Exception ex) {
        // Return cached or default response
        return Arrays.asList(new Order("Fallback-Order"));
    }
}

4. Controller Implementation

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    @Autowired
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping
    public ResponseEntity<List<Order>> getOrders() {
        return ResponseEntity.ok(orderService.getOrders());
    }
}

5. Circuit Breaker Configuration Class

@Configuration
public class CircuitBreakerConfig {

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .slidingWindowSize(2)
            .build();

        return CircuitBreakerRegistry.of(circuitBreakerConfig);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Advanced Features

1. Customizing Circuit Breaker Behavior

@CircuitBreaker(name = "orderService", 
                fallbackMethod = "fallbackForGetOrders",
                ignore = {IllegalArgumentException.class})

2. Event Monitoring

@PostConstruct
public void init() {
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("orderService");

    circuitBreaker.getEventPublisher()
        .onSuccess(event -> log.info("Call succeeded"))
        .onError(event -> log.error("Call failed"))
        .onStateTransition(event -> log.info("State changed: {}", event.getStateTransition()));
}

3. Metrics Integration

@Bean
MeterRegistry meterRegistry() {
    return new SimpleMeterRegistry();
}

@PostConstruct
public void init() {
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("orderService");
    TaggedCircuitBreakerMetrics
        .ofCircuitBreakerRegistry(circuitBreakerRegistry)
        .bindTo(meterRegistry);
}

Use Cases

  1. External API Calls
@CircuitBreaker(name = "externalApiService")
public ApiResponse callExternalApi() {
    return restTemplate.getForObject("http://external-api/data", ApiResponse.class);
}
  1. Database Operations
@CircuitBreaker(name = "databaseService")
public List<User> getAllUsers() {
    return userRepository.findAll();
}
  1. File Operations
@CircuitBreaker(name = "fileService")
public String readFile(String path) {
    return Files.readString(Path.of(path));
}

Best Practices

  1. Configuration Guidelines
  • Set appropriate window size based on traffic
  • Configure meaningful failure thresholds
  • Use appropriate wait duration
  • Include relevant exceptions only
resilience4j:
  circuitbreaker:
    instances:
      orderService:
        slidingWindowSize: 100
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
  1. Error Handling
public List<Order> fallbackForGetOrders(Exception ex) {
    log.error("Circuit breaker fallback triggered", ex);
    if (ex instanceof TimeoutException) {
        return getCachedOrders();
    }
    return getDefaultOrders();
}
  1. Monitoring Setup
@Bean
public ActuatorEndpoint circuitBreakerEndpoint(
        CircuitBreakerRegistry circuitBreakerRegistry) {
    return new CircuitBreakerEndpoint(circuitBreakerRegistry);
}

Common Scenarios and Solutions

  1. Handling Timeouts
@CircuitBreaker(name = "timeoutService")
@Timeout(value = 2, unit = ChronoUnit.SECONDS)
public Response slowOperation() {
    return externalService.call();
}
  1. Bulk Operations
@CircuitBreaker(name = "bulkService")
public List<Response> bulkOperation(List<Request> requests) {
    return requests.stream()
        .map(this::processSingle)
        .collect(Collectors.toList());
}
  1. Combining with Retry Pattern
@CircuitBreaker(name = "retryService")
@Retry(name = "retryService")
public Response operationWithRetry() {
    return externalService.call();
}

Leave a Reply

Your email address will not be published. Required fields are marked *