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 ?