В разрабатываемом приложении мне нужно выполнить огромное количество REST-вызовов.Архитектура ресурсов REST API, с которыми мне нужно взаимодействовать, является иерархической и выглядит следующим образом:
/api/continents - return list of all Earth's continents
/api/continents/{continent_name}/countries - return list of all countries on mentioned continent
/api/continents/{continent_name}/countries/{country_name}/cities - return list of all cities in mentioned country
К сожалению, этот API не предоставляет никаких методов для получения уже всех городов, и мне сначала нужно получить список всехКонтинент, после этого получить список всех стран для каждого континента, а затем получить список всех городов для каждой страны каждого континента.
Сначала я попытался реализовать свой метод получения всех городов из этого APIбез распараллеливания только при последовательных вызовах.Примерно так:
private List<City> getCities() {
List<Continent> continents = getAllContinents(); //HTTP GET call
List<Country> countries = new ArrayList<>();
for (Continent continent: continents) {
countries.addAll(getAllCountriesOfContinent(continent));
}
List<City> cities = new ArrayList<>();
for (Country country : countries) {
cities.addAll(getAllCitiesOfCountry(country));
}
return cities;
}
Но такой подход работал слишком медленно (в конкретных числах он выполнялся около 7 часов).Я решил попробовать улучшить его, используя Java Parallel Streams и CompletableFuture, и получил такие методы:
private List<City> getCities() {
return getAllContinents()
.parallelStream()
.map(continent -> getAllCountriesOfContinent(continent))
.flatMap(feature -> feature.join().parallelStream())
.map(country -> getAllCitiesOfCountry(country))
.flatMap(feature -> feature.join().parallelStream())
.collect(Collectors.toList());
}
Где методы getAllCountriesOfContinent и getAllCitiesOfCountry возвращают списки CompletableFuture и выглядят так:
private CompletableFuture<List<Country>> getAllCountriesOfContinent(Continent continent) {
return CompletableFuture.supplyAsync(() -> {
return restClient.getDataFromApi(continent);
});
}
private CompletableFuture<List<City>> getAllCitiesOfCountry(Country country) {
return CompletableFuture.supplyAsync(() -> {
return restClient.getDataFromApi(country);
});
}
СПри таком рефакторинге у меня получился хороший прирост производительности (он занимал около 25-30 минут).Но я думаю, что я мог бы улучшить его, используя Java ThreadPoolExecutors и Threads или инфраструктуру ForkJoin.Помогут ли такие подходы повысить производительность моего кода или для этого есть какие-то другие специальные методы / алгоритмы / инфраструктуры?