“I paint with a brayer and press. A color lover with monochrome moods”. -Kathleen DeMeo
Dependency
Maven POM
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
Is this gonna work?
Option A - Spring YAML Configuration
# resiliency4j dependency's properties configuration
resilience4j:
circuitbreaker:
instances:
circuitBreakerService:
slidingWindowSize: 10 # examine every last 10 requests in closed state
permittedNumberOfCallsInHalfOpenState: 5 # examine every last 5 requests in half open state
failureRateThreshold: 50 # 50% requests failed, will change to open state
waitDurationInOpenState: 10000 # circuit becomes half open after 10 seconds
registerHealthIndicator: true
timelimiter:
instances:
circuitBreakerService:
timeoutDuration: 10s
# cloud gateway's properties configuration
spring:
cloud:
gateway:
routes:
- id: yaml_route_id
uri: ${properties.upstream-service-url}
predicates:
- Method=POST,PATCH
- Path=/v2/api/resources,/v2/api/resources/{id}
- ReadBodyToString=String.class
filters:
- UpstreamServiceApiFilter
- name: Retry
args:
methods:
- POST
- PATCH
retries: ${properties.retry-times}
backoff:
firstBackoff: ${properties.retry-first-backoff}
maxBackoff: ${properties.retry-max-backoff}
- name: CircuitBreaker
args:
name: circuitBreakerService
fallbackUri: forward:/circuit-breaker-fallback
# custom properties
properties:
upstream-service-url: http://upstream-api
retry-times: 3
retry-first-backoff: 50ms
retry-max-backoff: 500ms
Option B - Spring Java Bean Configuration
private final ApplicationProperties properties;
private final UpstreamServiceApiFilter upstreamServiceApiFilter;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("java_route_id", r -> r.path("/v2/api/resources", "/v2/api/resources/{id}").and().method("POST", "PATCH")
.filters(f -> f.modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, body) -> {
// Do whatever you want with the request body here.
})
.filter(upstreamServiceApiFilter.apply(new UpstreamServiceApiFilter.Config()))
.retry(config -> {
var backoffConfig = new RetryGatewayFilterFactory.BackoffConfig();
backoffConfig.setFirstBackoff(properties.getRetryFirstBackoff());
backoffConfig.setMaxBackoff(properties.getRetryMaxBackoff());
config.setMethods(HttpMethod.POST, HttpMethod.PATCH)
.setRetries(properties.getRetryTimes())
.setBackoff(backoffConfig);
})
.circuitBreaker(config -> config.setName("circuitBreakerService").setFallbackUri("forward:/circuit-breaker-fallback"))
).uri(properties.getUpstreamServiceUrl())
)
.build();
}
Spring Cloud Gateway Filter
@Slf4j
@Component
public class UpstreamServiceApiFilter extends AbstractGatewayFilterFactory<UpstreamServiceApiFilter.Config> {
@Override
public GatewayFilter apply(UpstreamServiceApiFilter.Config config) {
return (exchange, chain) -> {
// Do your business logic for the filter
return chain.filter(exchange).doFinally(signalType -> {
HttpStatus httpStatus = exchange.getResponse().getStatusCode();
if (!httpStatus.is2xxSuccessful()) {
log.warn("Got this response code: {}", httpStatus.value());
// Do final process if it's bad response from upstream services
}
});
};
}
public static class Config {
// Put the configuration properties
}
}
Spring Cloud Gateway fallback controller
@RestController
@AllArgsConstructor
public class FallbackController {
private final ObjectMapper objectMapper;
@RequestMapping(value = "/circuit-breaker-fallback", method = {GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE})
Flux<String> getFallback(ServerHttpResponse response) throws JsonProcessingException {
response.setStatusCode(SERVICE_UNAVAILABLE);
return Flux.just(objectMapper.writeValueAsString(Map.of(
"code", "503.1.123",
"message", "Customized Circuit Breaker Fallback Message."
)));
}
}
Happy coding!