error handling with reactiveFeignClient and CircuitBreaker

1k views Asked by At

we are using reactive feign client (com.playtika.reactivefeign:feign-reactor-spring-cloud-starter:3.2.0)

circuit breaker version : org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j:2.1.0

and spring boot application version org.springframework.boot’ version ’2.6.6

when we get an error from reactive feign client (such as 404 error)

@ReactiveFeignClient(name = "someRestClient", url = "${react-gpi-service.url}",configuration = AuthConfigurationsomeRestClient.class, fallbackFactory = someRestClienttFallbackFactory.class)
@Profile("!test")
public interface someRestClient {
@PostMapping(value = "/v2/{entity}/any", produces = MediaType.ALL_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
 Mono<String> any(@PathVariable(value = "entity")

it goes over the error decoder to check if it should be retried

@Slf4j
@RequiredArgsConstructor
public class RetryableErrorDecoder implements ErrorDecoder {

    private static ErrorDecoder defaultErrorDecoder = new Default();
    private final String clientName;

    public Exception decode(String methodKey, Response response) {
        String body = "";
        try {
            body = IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("failed to parse error response body", e);
        }

        log.error("In RetryableErrorDecoder, got an error from {}. status: {}, body: {}, reason: {}, request: {}",
                clientName, response.status(), body, response.reason(), response.request());

        if (response.status() == HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE ||
                response.status() == HttpStatusCodes.STATUS_CODE_BAD_GATEWAY) {
            log.warn("Retry on error 503 or 502");
            return createRetryableException(response, "Service Unavailable 503, 502");
        } else {
            Exception decode = defaultErrorDecoder.decode(methodKey, response);
            if (decode instanceof FeignException &&
                    decode.getMessage().contains("authorizing")) {
                log.warn("Retry on {}", decode.getMessage());
                return createRetryableException(response, "Service authorizing problem");
            }
            return decode;
        }
    }

    private Exception createRetryableException(Response response, String message) {
        return new RetryableException(
                response.status(),
                message,
                response.request().httpMethod(),
                null,
                null,
                response.request());
    }
}

after that it goes to Circuit beaker predicate

public class someFailurePredicate  implements Predicate<Throwable> {

    @Override
    public boolean test(Throwable throwable) {
        return throwable instanceof ThirdPartyException
                || throwable instanceof ReadTimeoutException
                || throwable instanceof OutOfRetriesException;
    }
}

and then it goes to fallBackFactory mechanism because the circuit breaker requires the fallback method so the circuit breaker predicate is activated again.

@Component
public class someRestClientFallbackFactory implements FallbackFactory<someRestClient> {

    @Override
    public someRestClient apply(Throwable throwable) {
        return new someRestClientFallback(throwable);
    }
}

public class someRestClientFallback implements someRestClient {

    private final Throwable cause;

    public someClientFallback(Throwable cause) {
        this.cause = cause;
    }

    public Mono<String> performSearchRequest(String entity,
                                                     ) {
        return Mono.error(cause);
    }
}

because we have 2 mechanisms of error handling the circuit predicate is calling twice and duplicating the error.

I tried to move the retry mechanism(error decoder) to fallback method but the fallbackfactory method accepts throwable and reactiveFeignClientException doesn't have a status code and it's hard to determine if we should do the retry.

if I remove the fallback method I get this error message :

org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException: No fallback available.

we need to add it but then we have two mechanisms and a duplicate circuit breaker predicate count

1

There are 1 answers

0
Andrei Bulshtein On BEST ANSWER

Reactive Feign Client enables its own CB by default, it is possible to disable it by setting reactive.feign.circuit.breaker.enabled to false - https://github.com/PlaytikaOSS/feign-reactive/blob/develop/feign-reactor-spring-configuration/README.md