javaspring-bootmavencircuit-breakerresilience4j

Difficulty using Resilience4j


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>

Solution

  • 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.