How to properly configure fallback and retryer for @FeignClient?

Viewed 1360

I have a problem with configuring fallbacks for feign clients. Lets say I have client like this:

@FeignClient(name = "some-client", url = "${some-client.url}")
public interface SomeClient {

    @GetMapping(value = "/api/some-endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
    List<SomeDto> searchForAllDtos(@RequestParam("param") String param);
}

I have configured simple retryer like this:

@Configuration
public class ClientConfiguration {

    @Bean
    public Retryer retryer(@Value("${http.retry.period}") long period,
                           @Value("${http.retry.maxPeriod}") long maxPeriod,
                           @Value("${http.retry.maxAttempts}") int maxAttempts) {
        return new CustomRetryer(period, maxPeriod, maxAttempts);
    }

    @Bean
    public ErrorDecoder decoder() {
        return new CustomDecoder();
    }
}

@Slf4j
class CustomRetryer extends Retryer.Default {
    private final int maxAttempts;
    private final long period;
    private final long maxPeriod;

    public CustomRetryer(long period, long maxPeriod, int maxAttempts) {
        super(period, maxPeriod, maxAttempts);
        this.period = period;
        this.maxPeriod = maxPeriod;
        this.maxAttempts = maxAttempts;
    }

    private CustomRetryer(CustomRetryer retryer) {
        super(retryer.period, retryer.maxPeriod, retryer.maxAttempts);
        this.period = retryer.period;
        this.maxPeriod = retryer.maxPeriod;
        this.maxAttempts = retryer.maxAttempts;
    }

    @Override
    public void continueOrPropagate(RetryableException ex) {
        super.continueOrPropagate(ex);
        log.warn("Retrying HTTP request...");
    }

    @Override
    public Retryer clone() {
        return new CustomRetryer(this);
    }
}

class CustomDecoder implements ErrorDecoder {
    public Exception decode(String methodKey, Response response) {
        if (response.status() > 499) {
            throw new RetryableException(
                    response.status(),
                    String.format("Service unavailable (status code %s)", response.status()),
                    response.request().httpMethod(),
                    null,
                    response.request());
        } else {
            return new RuntimeException(
                    String.format("Error with status code %s received after HTTP request", response.status()));
        }
    }
}

Everything worked well until I wanted to add fallback when call is still failing after configured max attempts. I have changed my client like this:

@FeignClient(name = "some-client", url = "${some-client.url}", fallback = SomeClientFallback.class)
public interface SomeClient {
    // etc.
}

@Component
class SomeClientFallback implements SomeClient {

    @Override
    public List<SomeDto> searchForAllDtos(String param) {
        return Collections.emptyList();
    }
}

It was not working, exception was still thrown instead of routing to callback, I have read that hystrix must be enabled so I have added feign.hystrix.enabled=true to my application.properties file. Everything seems to worked well by after investigation and writing some tests I have found out that retry is only used once when I have configured it for 5 max attempts. Looks like there is some conflict with hystrix timeout which is set to 1000ms by default. I have tried to increase it via hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds prop but looks like it has no effect. Did I miss something?

There is spring-retry project which I could use but it requires to add @Retryable annotation to evey method and I would like to have default retry for all feign clients. I would appreciate all help and ideas.

0 Answers
Related