Трудно следить за вашим кодом, потому что вы смешиваете и сопоставляете реактивное программирование с императивным программированием не лучшим образом.
Ваш код не компилируется, и у вас есть несколько странных вещей, таких как medID
, которые никогда не используются, и переменные, которые никогда не объявлялись, например body
. Поэтому я взял ваш код только как есть, я не создал полностью рабочий пример, а только руководство.
Вы должны выбрать либо реактивный маршрут, либо императивный маршрут. Некоторые части моего ответа будут самоуверенными, если кто-то позже пожалуется, так что это отказ от ответственности.
Во-первых, вы по каждому запросу создаете несколько WebClients
, это, на мой взгляд, считается плохой практикой. Создание WebClient
является своего рода дорогостоящей ненужной операцией, поскольку вы можете использовать их повторно, поэтому вам следует объявить свои веб-клиенты во время запуска и @Autowire
их.
@Configuration
public class WebClientConfig {
@Bean
@Qualifier("WebClient1")
public WebClient createWebClient1() {
return WebClient.create( ... );
}
@Bean
@Qualifier("WebClient2")
public WebClient createWebClient2() {
return WebClient.create( ... );
}
@Bean
@Qualifier("WebClient3")
public WebClient createWebClient3() {
return WebClient.create( ... );
}
}
А затем используйте их, автоматически подключив их к вашему классу.
После очистки вашего кода и разделения его на функции с правильным возвратом, я надеюсь, это даст вам некоторое представление о том, как бы я его структурировал. Ваша проблема в том, что вы не возвращаетесь должным образом из своих функций и не выполняете цепочку возвратов. Как только вам нужно использовать subscribe
, вы обычно понимаете, что сделали что-то не так.
@RestController
public class FooBar {
private WebClient webClient1;
private WebClient webClient2;
private WebClient webClient3;
@Autowire
public Foobar(@Qualifier("WebClient1") WebClient webclient1, @Qualifier("WebClient2") WebClient webclient2, @Qualifier("WebClient3") WebClient webclient3) {
this.webClient1 = webClient1;
this.webClient2 = webClient2;
this.webClient3 = webClient3;
}
@GetMapping("monoMedication/{medID}")
public Mono<List<MedicationData>> getMonoMedication(@PathVariable String medID) {
return doLogin()
.flatMap(login -> {
return getMedicationBundles(login.getSessionId());
}).flatMap(medicationBundles -> {
return getMedicationData(medicationBundle.getId());
}).collectList();
}
private Mono<Login> doLogin() {
return webClient1
.post()
.uri("URI_LOGIN_API1")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(body)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Login.class);
}
private Flux<MedicationBundle> getMedicationBundles(String sessionId) {
return webClient2
.post()
.uri("URI_API1_GET_DATA")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue("Information")
.header("Authorization", sessionId)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToFlux(MedicationBundle.class);
}
private Mono<String> getMedicationData(String medicationId) {
return webClient3.get()
.uri(uriBuilder - > uriBuilder
.path("/URI_API2_GET_DATA/{medicationId}")
.build(medicationId))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(MedicationData.class);
}
}
Я написал это от руки без какой-либо IDE, это больше для того, чтобы показать вам, как вы должны структурировать свой код, надеюсь, что это покажет вам некоторые рекомендации.
Что можно и чего нельзя делать в реактивном программировании:
Избегайте использования блоков try/catch, в реактивном программировании вы обычно либо возвращаете Mono.empty()
, чтобы игнорировать ошибку, либо возвращаете Mono.error()
, содержащее исключение.
Используйте flatMap
для асинхронной обработки, map
для синхронной обработки, есть несколько других операторов, таких как concatMap
и flatMapSequential
, которые сохраняют порядок, объясняя, что это отдельный ответ.
по возможности избегайте void
методов, всегда старайтесь использовать чистые функции (избегайте манипулирования списками в пустых функциях , вы делаете это с указателями в C++, а не в Java), вы можете вернуть Mono<Void>
из реактивной функции, объединив оператор .then()
.
Всегда используйте преимущества системы типов, если это возможно, старайтесь не сериализовать в Object
, если это возможно, создавайте объектное представление данных и сериализуйте в него.
Почти никогда не подписывайтесь в своем приложении, если вы не являетесь потребителем данных. Тот, кто инициирует вызов, обычно имеет номер subscriber
, ваше приложение обычно имеет номер producer
, а вызывающий клиент (веб-сервис или другой сервис) — номер subscriber
. Несколько subscribers
обычно являются запахом кода.
Всегда старайтесь возвращаться и цепляться за возвраты. Все функции, которые я написал выше, что-то возвращают, и цепочка возвращается, это то, что называется construction belt analogy
. Я лично всегда начинаю каждую функцию с написания инструкции return в первой строке, затем я начинаю писать свои реактивные вещи. Никогда не покидайте Mono
или Flux
, не вернувшись к ним и не привязавшись к ним, потому что Mono
или Flux
никогда не запустятся, если на них никто не подпишется.
структурируйте свой код в функции, написание функции бесплатно :)
Немного полезной литературы и видео:
Начало работы с Reactor
официальная документация Webflux (я предлагаю сначала просмотреть официальную документацию по реактору, эту документацию может быть трудно понять, если вы не знаете реактор достаточно хорошо)
Хорошее весеннее видео, в нем почти все, что я написал выше. Что можно и чего нельзя делать в реактивном программировании
Как было сказано ранее, это немного основано на мнении, но, надеюсь, это даст вам некоторые рекомендации.
18.07.2020
WebClient
? 20.07.2020