1

I am trying to implement Resilience4j programmatically and also forced state transition through a rest endpoint. But unfortunately Circuit Breaker is not working when I use Decorator. Interesting;y it works when I use this with WebClient.

My Controller Class:

@RestController
@RequestMapping("v1/profileinfo")
public class WebController {
    public static final Logger LOG = LoggerFactory.getLogger(WebController.class);
    
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;
    
    @Autowired
    private AppConstants appConstants;

    @Autowired
    private ProfileService profileService;
    
    @GetMapping("/{profId}")
    public ResponseEntity<ProfielInfo> getProfileInfo(@PathVariable("profId") Long profId){
    
        ProfielInfo resp = null;
        resp = profileService.getProfileInfo(profId);
        return new ResponseEntity<ProfielInfo>(resp, HttpStatus.OK);
    }
    
    @PutMapping("circuitbreaker/{state}")
    public String setCircuitBreaker(@PathVariable("state") String state) {
                
        if(state.toUpperCase().equals(appConstants.CB_CLOSED)) {
            circuitBreakerRegistry.circuitBreaker("benefitService").transitionToClosedState();
        }
        
        if(state.toUpperCase().equals(appConstants.CB_OPEN)) {
            circuitBreakerRegistry.circuitBreaker("benefitService").transitionToForcedOpenState();
        }
        
        return state;
    }

}

ProfileInfo service:

....
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.decorators.Decorators;

@Service
public class ProfileService {
    public static final Logger LOG = LoggerFactory.getLogger(ProfileService.class);
    
    @Autowired
    private CircuitBreakerConfig circuitBreakerConfig; //ADDED
    
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry; //ADDED
    
    @Autowired
    private UserInfoService userInfoService;
    
    @Autowired
    private BenefitService benefitService;

    public ProfielInfo getProfileInfo(Long profId) throws TimeoutException {
    
        CircuitBreaker circuitBreaker = circuitBreakerRegistry
                  .circuitBreaker("benefitService");
                
        ProfielInfo profileInfo = null;
        profileInfo = new ProfielInfo();
        
        UserInfo userInfo = null;
        userInfo = userInfoService.getUserInfo(profId);
        
        Benefit benefit = null;
        Supplier<Benefit> supplier = () -> benefitService.getBenefitInfo(profId);
        LOG.info("||||Calling benefitService...");
        benefit = Decorators.ofSupplier(supplier)
                  .withCircuitBreaker(circuitBreaker) //---> Why it's not Working here ??                 
                  .withFallback(e -> buildFallbackBenefitInfo(profId))
                  .get();
        
        profileInfo.setUserInfo(userInfo);
        profileInfo.setBenefit(benefit);
        
        LOG.info("---End of the ProfileService.getProfileInfo()---");
        return profileInfo;
    }
    
    public Benefit buildFallbackBenefitInfo(Long memId) {
        Benefit benefit = null;
        benefit = new Benefit();
        benefit.setBenefitId("00000");
        benefit.setMemeberId("00000");
        
        return benefit;
    }
}

BenefitService class which calls external api with webclient. Circuit breaker works here and takes my custom cinfigurations.

@Service
public class BenefitService {   
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @Autowired
    private WebClient benefitApiClient;
    
    public Benefit getBenefitInfo(long profId) {
        
        CircuitBreaker circuitBreaker = circuitBreakerRegistry
                  .circuitBreaker("benefitService");

        return benefitApiClient.get()
                .uri("/" + profId)
                .retrieve()
                .bodyToMono(Benefit.class)
                .timeout(Duration.ofSeconds(3))
                .transformDeferred(CircuitBreakerOperator.of(circuitBreaker)) // works here
                .block();
    }
}

My Circuit Breaker Configuration class:

@Configuration
public class CircuitBreakerConfigs {
    
    @Value("${circuitbreaker.failureRateThreshold}")
    private int failureRateThreshold;
    
    @Value("${circuitbreaker.slowCallRateThreshold}")
    private int slowCallRateThreshold;
    
    @Value("${circuitbreaker.waitDurationInOpenState}")
    private int waitDurationInOpenState;
    
    @Value("${circuitbreaker.slowCallDurationThreshold}")
    private int slowCallDurationThreshold;
    
    @Value("${circuitbreaker.permittedNumberOfCallsInHalfOpenState}")
    private int permittedNumHalfOpenState;
    
    @Value("${circuitbreaker.minimumNumberOfCalls}")
    private int minimumNumberOfCalls;   
    
    @Value("${circuitbreaker.slidingWindowSize}")
    private int slidingWindowSize;
             
    @Bean
    public CircuitBreakerConfig circuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
                .failureRateThreshold(failureRateThreshold)
                .slowCallRateThreshold(slowCallRateThreshold)
                .waitDurationInOpenState(Duration.ofSeconds(waitDurationInOpenState))
                .slowCallDurationThreshold(Duration.ofSeconds(slowCallDurationThreshold))
                .permittedNumberOfCallsInHalfOpenState(permittedNumHalfOpenState)
                .minimumNumberOfCalls(minimumNumberOfCalls)
                .slidingWindowType(SlidingWindowType.COUNT_BASED)
                .slidingWindowSize(slidingWindowSize)
                .enableAutomaticTransitionFromOpenToHalfOpen()
                .recordExceptions(IOException.class, TimeoutException.class)
                .build();
     }
    
    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        return CircuitBreakerRegistry.of(circuitBreakerConfig());
    }

}

application.yml:

server:
  port: 9099

management:
  endpoints:
    web:
      exposure:
        include: "*"
        
resilience4j.circuitbreaker:
  configs:
    default:
      registerHealthIndicator: true
 
circuitbreaker:
  failureRateThreshold: 50
  slowCallRateThreshold: 50
  waitDurationInOpenState: 60
  slowCallDurationThreshold: 60
  minimumNumberOfCalls: 1
  permittedNumberOfCallsInHalfOpenState: 3
  slidingWindowSize: 5

build.gradle:

plugins {
    id 'org.springframework.boot' version '2.7.3'
    id 'io.spring.dependency-management' version '1.0.13.RELEASE'
    id 'java'
}

group = 'com.demo.ref'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
    
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.7.2'     
    implementation group: 'io.github.resilience4j', name: 'resilience4j-spring-boot2', version: '1.5.0'
    implementation group: 'io.github.resilience4j', name: 'resilience4j-reactor', version: '1.5.0'
    implementation group: 'io.github.resilience4j', name: 'resilience4j-all', version: '1.5.0'
    implementation group: 'io.micrometer', name: 'micrometer-registry-prometheus', version: '1.9.3'
    
}

tasks.named('test') {
    useJUnitPlatform()
}

As I mentioned above the circuit breaker is not working when I use with Decorators from ProfileService class. But if I move it to BenefitService class and use with WebClient it works perfectly. My external api(Benefit API) has been set to delay for 10 seconds so this call will timeout here) Can someone please help me ?

Thomson Mathew
  • 419
  • 1
  • 9
  • 28

0 Answers0