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
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.
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
}
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();
}
}
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);
}
});
}
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.