BAEL-5798: Guide to Resilience4j with Spring Boot (#12854)
Co-authored-by: Tapan Avasthi <tavasthi@Tapans-MacBook-Air.local>
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
package com.baeldung.resilientapp;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import io.github.resilience4j.bulkhead.BulkheadFullException;
|
||||
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
|
||||
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
|
||||
|
||||
@ControllerAdvice
|
||||
public class ApiExceptionHandler {
|
||||
@ExceptionHandler({ CallNotPermittedException.class })
|
||||
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
public void handleCallNotPermittedException() {
|
||||
}
|
||||
|
||||
@ExceptionHandler({ TimeoutException.class })
|
||||
@ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
|
||||
public void handleTimeoutException() {
|
||||
}
|
||||
|
||||
@ExceptionHandler({ BulkheadFullException.class })
|
||||
@ResponseStatus(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)
|
||||
public void handleBulkheadFullException() {
|
||||
}
|
||||
|
||||
@ExceptionHandler({ RequestNotPermitted.class })
|
||||
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
|
||||
public void handleRequestNotPermitted() {
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.baeldung.resilientapp;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Component
|
||||
public class ExternalAPICaller {
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
public ExternalAPICaller(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public String callApi() {
|
||||
return restTemplate.getForObject("/api/external", String.class);
|
||||
}
|
||||
|
||||
public String callApiWithDelay() {
|
||||
String result = restTemplate.getForObject("/api/external", String.class);
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.baeldung.resilientapp;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class ExternalApiCallerConfig {
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplateBuilder().rootUri("http://localhost:9090")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.baeldung.resilientapp;
|
||||
|
||||
import org.jobrunr.autoconfigure.JobRunrAutoConfiguration;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(exclude = { JobRunrAutoConfiguration.class})
|
||||
public class ResilientApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ResilientApp.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.baeldung.resilientapp;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
|
||||
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.timelimiter.annotation.TimeLimiter;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/")
|
||||
public class ResilientAppController {
|
||||
|
||||
private final ExternalAPICaller externalAPICaller;
|
||||
|
||||
@Autowired
|
||||
public ResilientAppController(ExternalAPICaller externalApi) {
|
||||
this.externalAPICaller = externalApi;
|
||||
}
|
||||
|
||||
@GetMapping("/circuit-breaker")
|
||||
@CircuitBreaker(name = "CircuitBreakerService")
|
||||
public String circuitBreakerApi() {
|
||||
return externalAPICaller.callApi();
|
||||
}
|
||||
|
||||
@GetMapping("/retry")
|
||||
@Retry(name = "retryApi", fallbackMethod = "fallbackAfterRetry")
|
||||
public String retryApi() {
|
||||
return externalAPICaller.callApi();
|
||||
}
|
||||
|
||||
@GetMapping("/time-limiter")
|
||||
@TimeLimiter(name = "timeLimiterApi")
|
||||
public CompletableFuture<String> timeLimiterApi() {
|
||||
return CompletableFuture.supplyAsync(externalAPICaller::callApiWithDelay);
|
||||
}
|
||||
|
||||
@GetMapping("/bulkhead")
|
||||
@Bulkhead(name = "bulkheadApi")
|
||||
public String bulkheadApi() {
|
||||
return externalAPICaller.callApi();
|
||||
}
|
||||
|
||||
@GetMapping("/rate-limiter")
|
||||
@RateLimiter(name = "rateLimiterApi")
|
||||
public String rateLimitApi() {
|
||||
return externalAPICaller.callApi();
|
||||
}
|
||||
|
||||
public String fallbackAfterRetry(Exception ex) {
|
||||
return "all retries have exhausted";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,50 @@
|
||||
org.jobrunr.background-job-server.enabled=true
|
||||
org.jobrunr.dashboard.enabled=true
|
||||
org.jobrunr.dashboard.port=0
|
||||
|
||||
logging.level.root=DEBUG
|
||||
|
||||
management.health.circuitbreakers.enabled=true
|
||||
management.health.ratelimiters.enabled=true
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
spring.jackson.serialization.indent-output=true
|
||||
|
||||
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.failure-rate-threshold=50
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.minimum-number-of-calls=5
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.automatic-transition-from-open-to-half-open-enabled=true
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.wait-duration-in-open-state=5s
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.permitted-number-of-calls-in-half-open-state=3
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.sliding-window-size=10
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.sliding-window-type=count_based
|
||||
|
||||
resilience4j.circuitbreaker.metrics.enabled=true
|
||||
resilience4j.circuitbreaker.metrics.legacy.enabled=true
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.register-health-indicator=true
|
||||
resilience4j.circuitbreaker.instances.CircuitBreakerService.event-consumer-buffer-size=10
|
||||
|
||||
|
||||
resilience4j.retry.instances.retryApi.max-attempts=3
|
||||
resilience4j.retry.instances.retryApi.wait-duration=1s
|
||||
resilience4j.retry.metrics.legacy.enabled=true
|
||||
resilience4j.retry.metrics.enabled=true
|
||||
|
||||
resilience4j.timelimiter.metrics.enabled=true
|
||||
resilience4j.timelimiter.instances.timeLimiterApi.timeout-duration=2s
|
||||
resilience4j.timelimiter.instances.timeLimiterApi.cancel-running-future=true
|
||||
|
||||
resilience4j.bulkhead.metrics.enabled=true
|
||||
resilience4j.bulkhead.instances.bulkheadApi.max-concurrent-calls=3
|
||||
resilience4j.bulkhead.instances.bulkheadApi.max-wait-duration=1
|
||||
|
||||
resilience4j.ratelimiter.metrics.enabled=true
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.register-health-indicator=true
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.limit-for-period=5
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.limit-refresh-period=60s
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.timeout-duration=0s
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.allow-health-indicator-to-fail=true
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.subscribe-for-events=true
|
||||
resilience4j.ratelimiter.instances.rateLimiterApi.event-consumer-buffer-size=50
|
||||
Reference in New Issue
Block a user