Search code examples
springspring-bootspring-mvcspring-rest

How to make pause between each api call in springframework RestClient


This is a org.springframework.web.client.RestClient config

@Configuration
public class CloudConfig {
    @Value("${experimental.host}")
    String baseURI;
    @Bean
    RestClient restClient(RestClient.Builder builder){
        return builder.baseUrl(baseURI).build();
    }
}

This is service which does a call to external API

@Component
public class RequestService {

    public String callAPI(int userID){
        Map<String, Object> properties = Map.of("id", userID);
        return restClient.post()
                .uri("/external")
                .body(properties)
                .retrieve()
                .body(String.class);
    }
}

I get a list of user from db and in the loop call external API

@Service
public class RabbitMQProducer {

    private UserRepository repository;
    private RequestService requestService;

    @Scheduled(fixedRate = 10000)
    public void sendUserData(){
        for(User user : repository.findAll()) {
            String data = requestService.callAPI(user.getID);
            ......
        }
    }   
}

What is a correct way to make a pause between each call because external api has constraine of 1 second to call ? I got error message from API "org.springframework.web.client.HttpClientErrorException$TooManyRequests: too many request 2 times in 1000 millseconds"

Is there any pattern or solution how to resolve that kind of problem ?

I think expected behavior:

call API --> 1 second wait --> call API --> 1 second wait ...

Easy way to fix that it's just to add Thread.sleep(1000) but i'm not sure that is a good solution


Solution

  • If you use Thread.sleep(1000), then it will make constraints of 1 call for 1 second, but it is not ideal because it will block the current thread.

    1. Webflux & webClient

    I use this.

       public Mono<String> callAPI(User user) {
            return webClient.post()
                    .uri("/external")
                    .bodyValue(Map.of("id", user.getID()))
                    .retrieve()
                    .bodyToMono(String.class)
                    .delayElement(Duration.ofSeconds(1)); // Delay for each subscriber
        }
    

    2. Resilience4j

    This is the rate-limiting library.

    import io.github.resilience4j.ratelimiter.RateLimiter;
    import io.github.resilience4j.ratelimiter.RateLimiterConfig;
    import java.time.Duration;
    
    @Configuration
    public class RateLimiterConfig {
    
        @Bean
        public RateLimiter rateLimiter() {
            RateLimiterConfig config = RateLimiterConfig.custom()
                .limitRefreshPeriod(Duration.ofSeconds(1))
                .limitForPeriod(1)
                .timeoutDuration(Duration.ofMillis(500))
                .build();
    
            return RateLimiter.of("apiRateLimiter", config);
        }
    }
    

    .

    @Service
    public class RateLimitedRequestService {
    
        private final RequestService requestService;
        private final RateLimiter rateLimiter;
    
        public RateLimitedRequestService(RequestService requestService, RateLimiter rateLimiter) {
            this.requestService = requestService;
            this.rateLimiter = rateLimiter;
        }
    
        public String callAPI(User user) {
            return RateLimiter.decorateSupplier(rateLimiter, () -> requestService.callAPI(user.getID())).get();
        }
    }
    

    2. @Async

    You should add @EnableAsync

    @Async
    public CompletableFuture<String> callAPIWithDelay(User user, long delay) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(delay);
                return requestService.callAPI(user.getID());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IllegalStateException(e);
            }
        });
    }
    

    +a, retry

    I recommend you add retry to ensure that works properly. When working with external API that has a rate limiting, it is recommended to make some retry logic.