CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

examples.mdplugins/developer-kit-java/skills/spring-boot-resilience4j/references/

Resilience4j Real-World Examples

E-Commerce Order Service

Complete example demonstrating all Resilience4j patterns in a microservices environment.

Project Structure

order-service/
├── src/main/java/com/ecommerce/order/
│   ├── config/
│   │   ├── ResilienceConfig.java
│   │   └── RestTemplateConfig.java
│   ├── controller/
│   │   ├── OrderController.java
│   │   └── GlobalExceptionHandler.java
│   ├── service/
│   │   ├── OrderService.java
│   │   ├── PaymentService.java
│   │   ├── InventoryService.java
│   │   └── NotificationService.java
│   ├── domain/
│   │   ├── Order.java
│   │   ├── OrderStatus.java
│   │   └── Payment.java
└── src/main/resources/
    └── application.yml

Configuration

server:
  port: 8080

spring:
  application:
    name: order-service

resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
    instances:
      paymentService:
        baseConfig: default
        waitDurationInOpenState: 60s
      inventoryService:
        baseConfig: default

  retry:
    configs:
      default:
        maxAttempts: 3
        waitDuration: 500ms
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2
    instances:
      paymentService:
        maxAttempts: 5
        waitDuration: 1s

  ratelimiter:
    configs:
      default:
        limitForPeriod: 100
        limitRefreshPeriod: 1s
    instances:
      emailService:
        limitForPeriod: 10
        limitRefreshPeriod: 1m

  bulkhead:
    configs:
      default:
        maxConcurrentCalls: 10
        maxWaitDuration: 100ms
    instances:
      orderProcessing:
        maxConcurrentCalls: 5

  timelimiter:
    configs:
      default:
        timeoutDuration: 3s
    instances:
      paymentService:
        timeoutDuration: 5s

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
  health:
    circuitbreakers:
      enabled: true
    ratelimiters:
      enabled: true

Order Service Implementation

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {

    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final NotificationService notificationService;

    @Bulkhead(name = "orderProcessing", type = Bulkhead.Type.SEMAPHORE)
    @Transactional
    public Order processOrder(OrderRequest request) {
        log.info("Processing order for customer: {}", request.getCustomerId());

        Order order = createOrder(request);

        try {
            // Reserve inventory
            inventoryService.reserveInventory(order);

            // Process payment
            String paymentId = paymentService.processPayment(order).get();
            order = order.toBuilder().paymentId(paymentId).build();

            // Send confirmation (async, best effort)
            notificationService.sendOrderConfirmation(order);

            log.info("Order processed successfully: {}", order.getId());
            return order;

        } catch (Exception ex) {
            log.error("Order processing failed", ex);
            compensateFailedOrder(order);
            throw new OrderProcessingException("Failed to process order", ex);
        }
    }

    private void compensateFailedOrder(Order order) {
        try {
            inventoryService.releaseInventory(order);
            if (order.getPaymentId() != null) {
                paymentService.refundPayment(order.getPaymentId());
            }
        } catch (Exception ex) {
            log.error("Compensation failed", ex);
        }
    }
}

Payment Service with Multiple Patterns

@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentService {

    private final PaymentClient paymentClient;

    @CircuitBreaker(name = "paymentService", fallbackMethod = "processPaymentFallback")
    @Retry(name = "paymentService")
    @TimeLimiter(name = "paymentService")
    public CompletableFuture<String> processPayment(Order order) {
        return CompletableFuture.supplyAsync(() -> {
            Payment payment = Payment.builder()
                .orderId(order.getId())
                .amount(order.getTotalAmount())
                .build();

            PaymentResponse response = paymentClient.processPayment(payment);

            if (!response.isSuccess()) {
                throw new PaymentFailedException(response.getErrorMessage());
            }

            return response.getPaymentId();
        });
    }

    private CompletableFuture<String> processPaymentFallback(
            Order order, Exception ex) {
        log.error("Payment processing failed for order: {}", order.getId(), ex);
        throw new PaymentServiceUnavailableException(
            "Payment service unavailable", ex);
    }

    @CircuitBreaker(name = "paymentService")
    @Retry(name = "paymentService")
    public void refundPayment(String paymentId) {
        paymentClient.refundPayment(paymentId);
    }
}

Exception Handler

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CallNotPermittedException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorResponse handleCircuitOpen(CallNotPermittedException ex) {
        log.error("Circuit breaker is open", ex);
        return ErrorResponse.builder()
            .code("SERVICE_UNAVAILABLE")
            .message("Service is temporarily unavailable")
            .status(HttpStatus.SERVICE_UNAVAILABLE.value())
            .build();
    }

    @ExceptionHandler(RequestNotPermitted.class)
    @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
    public ErrorResponse handleRateLimited(RequestNotPermitted ex) {
        return ErrorResponse.builder()
            .code("TOO_MANY_REQUESTS")
            .message("Rate limit exceeded")
            .status(HttpStatus.TOO_MANY_REQUESTS.value())
            .build();
    }

    @ExceptionHandler(BulkheadFullException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorResponse handleBulkheadFull(BulkheadFullException ex) {
        return ErrorResponse.builder()
            .code("SERVICE_BUSY")
            .message("Service at capacity")
            .status(HttpStatus.SERVICE_UNAVAILABLE.value())
            .build();
    }

    @ExceptionHandler(TimeoutException.class)
    @ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
    public ErrorResponse handleTimeout(TimeoutException ex) {
        return ErrorResponse.builder()
            .code("REQUEST_TIMEOUT")
            .message("Request timed out")
            .status(HttpStatus.REQUEST_TIMEOUT.value())
            .build();
    }
}

Testing Patterns

Unit Test for Circuit Breaker

@SpringBootTest
class PaymentServiceCircuitBreakerTest {

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @MockBean
    private PaymentClient paymentClient;

    private CircuitBreaker circuitBreaker;

    @BeforeEach
    void setup() {
        circuitBreaker = circuitBreakerRegistry.circuitBreaker("paymentService");
        circuitBreaker.reset();
    }

    @Test
    void shouldOpenCircuitAfterFailures() {
        Order order = createTestOrder();
        when(paymentClient.processPayment(any()))
            .thenThrow(new RuntimeException("Service error"));

        // Trigger failures to exceed threshold
        for (int i = 0; i < 5; i++) {
            try {
                paymentService.processPayment(order).get();
            } catch (Exception ignored) {}
        }

        assertThat(circuitBreaker.getState())
            .isEqualTo(CircuitBreaker.State.OPEN);

        // Next call should fail immediately
        assertThatThrownBy(() -> paymentService.processPayment(order).get())
            .hasRootCauseInstanceOf(PaymentServiceUnavailableException.class);
    }
}

Integration Test with WireMock

@SpringBootTest
@AutoConfigureWireMock(port = 0)
class OrderServiceIntegrationTest {

    @Autowired
    private OrderService orderService;

    @Test
    void shouldRetryOnTransientFailure() {
        // First two calls fail, third succeeds
        stubFor(post("/payment/process")
            .inScenario("Retry")
            .whenScenarioStateIs(STARTED)
            .willReturn(serverError())
            .willSetStateTo("First Retry"));

        stubFor(post("/payment/process")
            .inScenario("Retry")
            .whenScenarioStateIs("First Retry")
            .willReturn(serverError())
            .willSetStateTo("Second Retry"));

        stubFor(post("/payment/process")
            .inScenario("Retry")
            .whenScenarioStateIs("Second Retry")
            .willReturn(ok().withBody("{\"paymentId\":\"PAY-123\"}")));

        Order order = orderService.processOrder(createOrderRequest());

        assertThat(order.getPaymentId()).isEqualTo("PAY-123");
        verify(exactly(3), postRequestedFor(urlEqualTo("/payment/process")));
    }
}

Advanced Scenarios

Reactive WebFlux Example

@Service
@RequiredArgsConstructor
public class ReactiveProductService {

    private final WebClient webClient;
    private final CircuitBreaker circuitBreaker;
    private final Retry retry;

    public Mono<Product> getProduct(String productId) {
        return webClient.get()
            .uri("/products/{id}", productId)
            .retrieve()
            .bodyToMono(Product.class)
            .transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
            .transformDeferred(RetryOperator.of(retry))
            .onErrorResume(throwable ->
                Mono.just(Product.unavailable(productId))
            );
    }
}

Custom Resilience Configuration

@Configuration
@Slf4j
public class ResilienceConfig {

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofSeconds(30))
            .slowCallDurationThreshold(Duration.ofSeconds(2))
            .permittedNumberOfCallsInHalfOpenState(3)
            .minimumNumberOfCalls(5)
            .slidingWindowSize(10)
            .build();

        CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);

        // Register event consumer
        registry.getEventPublisher()
            .onEntryAdded(event ->
                log.info("CircuitBreaker added: {}",
                    event.getAddedEntry().getName())
            );

        return registry;
    }

    @Bean
    public RegistryEventConsumer<CircuitBreaker> circuitBreakerEventConsumer() {
        return new RegistryEventConsumer<>() {
            @Override
            public void onEntryAddedEvent(EntryAddedEvent<CircuitBreaker> event) {
                CircuitBreaker cb = event.getAddedEntry();
                cb.getEventPublisher()
                    .onStateTransition(e ->
                        log.warn("CircuitBreaker {} state changed: {} -> {}",
                            cb.getName(),
                            e.getStateTransition().getFromState(),
                            e.getStateTransition().getToState())
                    )
                    .onError(e ->
                        log.error("CircuitBreaker {} error: {}",
                            cb.getName(),
                            e.getThrowable().getMessage())
                    );
            }

            @Override
            public void onEntryRemovedEvent(EntryRemovedEvent<CircuitBreaker> event) {
                log.info("CircuitBreaker removed: {}",
                    event.getRemovedEntry().getName());
            }

            @Override
            public void onEntryReplacedEvent(EntryReplacedEvent<CircuitBreaker> event) {
                log.info("CircuitBreaker replaced: {}",
                    event.getNewEntry().getName());
            }
        };
    }
}

Monitoring and Metrics

@RestController
@RequestMapping("/api/monitoring")
@RequiredArgsConstructor
public class ResilienceMonitoringController {

    private final CircuitBreakerRegistry circuitBreakerRegistry;

    @GetMapping("/circuit-breakers")
    public List<CircuitBreakerStatus> getStatus() {
        return circuitBreakerRegistry.getAllCircuitBreakers().stream()
            .map(this::toStatus)
            .collect(Collectors.toList());
    }

    private CircuitBreakerStatus toStatus(CircuitBreaker cb) {
        CircuitBreaker.Metrics metrics = cb.getMetrics();

        return CircuitBreakerStatus.builder()
            .name(cb.getName())
            .state(cb.getState().name())
            .failureRate(metrics.getFailureRate())
            .slowCallRate(metrics.getSlowCallRate())
            .numberOfBufferedCalls(metrics.getNumberOfBufferedCalls())
            .numberOfFailedCalls(metrics.getNumberOfFailedCalls())
            .numberOfSuccessfulCalls(metrics.getNumberOfSuccessfulCalls())
            .build();
    }
}

@Value
@Builder
class CircuitBreakerStatus {
    String name;
    String state;
    float failureRate;
    float slowCallRate;
    int numberOfBufferedCalls;
    int numberOfFailedCalls;
    int numberOfSuccessfulCalls;
}

See testing-patterns.md for comprehensive testing strategies and configuration-reference.md for complete configuration options.

plugins

developer-kit-java

skills

README.md

tile.json