I'm trying to use resilience4j with springboot 3 in my application. but it doesn't arrive in the fallback method, can someone tell me what's wrong?
I tried to follow the official documentation, and I had no success,
Based on the code snippets, can anyone identify what is going wrong with the following code?
Application.properties
management.endpoints.web.exposure.include: '\*'
management.endpoint.health.show-details: always
management.health.diskspace.enabled: false
management.health.circuitbreakers.enabled: true
management.health.ratelimiters.enabled: false
info:
name: ${spring.application.name}
description: ecommerce pedidoapi
environment: ${spring.profiles.active}
version: 0.0.1
management.metrics.tags.application: ${spring.application.name}
management.metrics.distribution.percentiles-histogram.http.server.requests: true
management.metrics.distribution.percentiles-histogram.resilience4j.circuitbreaker.calls: true
resilience4j.circuitbreaker:
instances:
pedidoapi:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
Service.java
public static final String PEDIDOAPI = "pedidoapi";
public UserModel returnUserDataFallback(Exception userExeception){
System.out.println("Fallback");
return new UserModel( 1L, "user" ,"one", "111.111.111-23" , "2199999999", true, null, null);
}
@CircuitBreaker(name = PEDIDOAPI, fallbackMethod = "returnUserDataFallback")
public UserModel getUser(Long idUsuario) {
UserModel user = null;
user =userFeignClient.getById(idUsuario).getBody();
return user;
}
public Optional<PedidoModel> registerPedido(@Valid PedidoModel pedidoModel) throws Exception {
UserModel user = getUser(pedidoModel.getIdUsuario());
...
return Optional.of(pedido);
}
Controller.java
@PostMapping("/register")
public ResponseEntity<String> registerPedido(@Valid @RequestBody PedidoModel pedidoModel) throws Exception {
return pedidoService.registerPedido(pedidoModel)
.map(res -> ResponseEntity.status(HttpStatus.CREATED).body("Pedido criado com sucesso"))
.orElse(ResponseEntity.status(HttpStatus.BAD_REQUEST).build());
}
Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.blue</groupId>
<artifactId>pedidoapi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>pedidoapi</name>
<description>Api do pedido</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.14</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr-jackson</artifactId>
<version>0.10.3</version>
</dependency> -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
Spring AOP/Proxy is the reason for the fallback not being triggered. When annotated with @CircuitBreaker
a proxy instance of the service class is created and used. The proxies work in such a way that calls from one bean/class to another bean/class are intercepted, and it cannot intercept calls from method to method within the bean/class.
Hence workaround would be to move the circuit breaker annotated method and fallback method to a different class.
Downstream.java
public UserModel returnUserDataFallback(Exception userExeception){
System.out.println("Fallback");
return new UserModel( 1L, "user" ,"one", "111.111.111-23" , "2199999999", true, null, null);
}
@CircuitBreaker(name = PEDIDOAPI, fallbackMethod = "returnUserDataFallback")
public UserModel getUser(Long idUsuario) {
UserModel user = null;
user =userFeignClient.getById(idUsuario).getBody();
return user;
}
Also the fallback method would be triggered for any kind of Exception since it accepts Exception
as an argument. CallNotPermittedException
can be used which indicates the Circuit Breaker is in OPEN State.