#spring-boot #spring-webflux #project-reactor
Вопрос:
Я хочу иметь возможность извлечь Listlt;Payloadgt;
из Monolt;Listlt;Payloadgt;gt;
, чтобы передать его в нижестоящую службу для обработки (или, возможно, вернуть из read(RequestParams params)
метода, вместо того, чтобы он возвращался void
):
@PostMapping("/subset") public void read(@RequestBody RequestParams params){ Monolt;Listlt;Payloadgt;gt; result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword()); .... }
где reader.read(...)
находится метод в службе autowired Spring, использующий веб-клиент для получения данных из внешнего API веб-службы:
public Monolt;Listlt;Payloadgt;gt; read(String date, String assetClasses, String firmAccounts, String id, String password) { Fluxlt;Payloadgt; nodes = client .get() .uri(uriBuilder -gt; uriBuilder .path("/api/subset") .queryParam("payloads", true) .queryParam("date", date) .queryParam("assetClasses", assetClasses) .queryParam("firmAccounts", firmAccounts) .build()) .headers(header -gt; header.setBasicAuth("abc123", "XXXXXXX")) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -gt; { System.out.println("4xx error"); return Mono.error(new RuntimeException("4xx")); }) .onStatus(HttpStatus::is5xxServerError, response -gt; { System.out.println("5xx error"); return Mono.error(new RuntimeException("5xx")); }) .bodyToFlux(Payload.class); Monolt;Listlt;Payloadgt;gt; records = nodes .collectList(); return records; }
Выполнение блокировки result.block()
не допускается в WebFlux и создает исключение:
new IllegalStateException("block()/blockFirst()/blockLast() are blocking, which is not supported in thread ..." ;
Как правильно извлечь содержимое Моно в WebFlux? Это что-то вроде а subscribe()
? Каков будет синтаксис?
Заранее спасибо.
Ответ №1:
«Правильного пути» не существует, и в этом весь смысл. Чтобы получить значение, вам нужно заблокировать, а блокировка в webflux плохая по многим причинам (в которые я сейчас не буду вдаваться).
Что вам следует сделать, так это полностью вернуть издателя вызывающему клиенту.
Одна из вещей, которую многим обычно трудно понять, заключается в том, что webflux работает с производителем ( Mono
или Flux
) и подписчиком.
Вся ваша услуга также является производителем, и вызывающий клиент может рассматриваться как подписчик.
Думайте об этом как о длинной цепочке, которая начинается с источника данных и заканчивается в клиенте, отображающем данные.
Простое эмпирическое правило состоит в том, что кто бы ни был конечным потребителем данных, он является подписчиком, все остальные являются производителями.
Поэтому в вашем случае вы просто возвращаете Monolt;Listlt;Tgt;
сообщение вызывающему клиенту.
@PostMapping("/subset") public Monolt;Listlt;Payloadgt;gt; read(@RequestBody RequestParams params){ Monolt;Listlt;Payloadgt;gt; result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword()); return result; }
Комментарии:
1. @Toerktumalare Как же тогда получить
Listlt;Payloadgt;gt;
выход из этогоMono
в конце этой цепочки, поскольку блокировка запрещена?2. если вы этого не сделаете, сама платформа подпишется на вашу цепочку и выдаст ответ вызывающему клиенту. Почему вы чувствуете такую потребность в получении
Listlt;Tgt;
, если вам нужно работать со списком, который вы используетеMono#map
, илиMono#flatMap
нет необходимости обращаться к списку внеMono
контекста3. Клиенту, получающему результат
read()
вызова, нужен список, который он затем может передать по потоку, поэтому мне нужно извлечь список и вернуть его как тип. Вопрос в том, как это сделать сmap
flatMap
полученным Моно или на нем?4. @SimeonLeyzerzon ваш вопрос не имеет смысла, я надеюсь, вы об этом знаете. Поскольку функция помечена символом a
@PostMapping
, я предполагаю, что ваш клиент делаетPOST
запрос. Вы написалиI need to extract a List and return it as a type
, что вы имеете в виду, что вам нужно вернуть его как тип? Вам нужно, чтобы платформа сериализовала все, что вы возвращаете, в формат, который использует ваш клиент.5. Код, который я опубликовал, который вы даже не пробовали (иначе вы бы не написали свой странный вопрос), автоматически сериализует http-ответ, содержащий список в формате json, содержащий объект
Payload
. Пожалуйста, попробуйте код, прежде чем задавать какие-либо дополнительные вопросы. я повторю это еще раз, вы ВООБЩЕ НИКАК НЕ можете получить aListlt;Payloadgt;
без блокировки… ни за что, ни за что, ноль, никогда.
Ответ №2:
В то время как следующее возвращает значение Моно, наблюдаемое в журналах:
@PostMapping("/subset") @ResponseBody public Monolt;ResponseEntitylt;Listlt;Payloadgt;gt;gt; read1(@RequestBody RequestParams params){ Monolt;Listlt;Payloadgt;gt; result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword()); return result .map(e -gt; new ResponseEntitylt;Listlt;PayloadByStandardBasisgt;gt;(e, HttpStatus.OK)); }
понимание, которое я искал, заключалось в правильном способе составления цепочки вызовов с помощью WebFlux, при котором ответ от одного из его операторов/ветвей (материализованный в результате вызова webclient, создающий набор записей, как указано выше) может быть передан другому оператору/ветви, чтобы облегчить побочный эффект сохранения этих записей в БД или что-то в этом роде.
Вероятно, было бы хорошей идеей смоделировать каждый из этих шагов как отдельную конечную точку REST, а затем иметь другую конечную точку для операции компоновки, которая внутренне вызывает каждую независимую конечную точку в правильном порядке, или другие варианты проектирования были бы более предпочтительными?
В конечном счете, это то понимание, которое я искал, поэтому, если кто-нибудь захочет поделиться примером кода, а также мнениями о том, как лучше реализовать описанный выше набор шагов, я готов принять наиболее исчерпывающий ответ.
Спасибо.