BOTER REACTIVO DE PRIMAVERA: RETORNO EVALUA ANTES DE MAPA FINSHED -- spring campo con spring-webflux campo con spring-webclient campo con spring-reactive camp Relacionados El problema

Reactive Spring Boot: return evaluates before map finshed


0
vote

problema

Español

Estoy usando un webcliente reactivo para construir una API que se comunica con otras 2 API. API2 necesita obtener información de API1, y luego mi servicio combina y devuelve tanto la información. Recurso:

  @GetMapping("monoMedication/{medID}")     public  Mono<Object> getMonoMedication(@PathVariable String medID) throws SSLException {            Mono<Login> Login =createWebClient()                 .post()                 .uri("URI_LOGIN_API1" )                 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)                 .body(BodyInserters.fromObject(body))                 .retrieve()                 .bodyToMono(Login.class);          return Login.map(login-> {                        Mono<String> medicationBundles = null;             try {                  medicationBundles = createWebClient()                         .post()                         .uri("URI_API1_GET_DATA")                         .contentType(MediaType.APPLICATION_JSON)                         .accept(MediaType.APPLICATION_JSON)                         .body(BodyInserters.fromObject("Information"))                         .header("Authorization", login.getSessionId())                         .retrieve()                        .bodyToMono(String.class);              } catch (SSLException e) {                 e.printStackTrace();             }              return  medicationBundles.map(bundles_string -> {                 try {                     List<Object> bundle_list = mapper.readValue(bundles_string, new TypeReference<List<Object>>(){});                     bundle_list.forEach(bundle-> processBundle(bundle,medicationList));                      return medicationList;                 } catch (JsonProcessingException e) {                     e.printStackTrace();                 }                     return null;                 })         })     }    

La función:

  List<String> medicationList = new ArrayList<>();     private void processBundle(Object bundle, List<String> medicationlist) {        //do something to get id from bundle         String ID = bundle.getID(); // if i add something to medicationList.add(ID) here, it is in the required return of my API         Mono<String> Medication =              webClientBuilder.build()             .get()             .uri("URI_API2_GET_DATA"+ID)             .retrieve()             .bodyToMono(String.class);           Medication.map(medication_ID -> {         //do something to get information from medication_ID             String info = medication_ID.getInfo();              //this Information comes after the required return             return medicationList.add(info+ID);         }).subscribe();     }   

Mi problema es que la devolución llega antes de que se complete el último mapa requerido. De alguna manera me falta algo. Probé diferentes enfoques con por ejemplo. Luego (), Luegomany (), ThenReturn () en diferentes posiciones. ¿Hay alguna forma de hacer esto? Si quizás ya haya un ejemplo simple terminado, ¡eso también ayudaría!

Original en ingles

I'm using reactive WebClient to build an API that communicates with 2 other APIs. API2 needs to get Information from API1, and then my service combines and returns both information. Resource:

@GetMapping("monoMedication/{medID}")     public  Mono<Object> getMonoMedication(@PathVariable String medID) throws SSLException {            Mono<Login> Login =createWebClient()                 .post()                 .uri("URI_LOGIN_API1" )                 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)                 .body(BodyInserters.fromObject(body))                 .retrieve()                 .bodyToMono(Login.class);          return Login.map(login-> {                        Mono<String> medicationBundles = null;             try {                  medicationBundles = createWebClient()                         .post()                         .uri("URI_API1_GET_DATA")                         .contentType(MediaType.APPLICATION_JSON)                         .accept(MediaType.APPLICATION_JSON)                         .body(BodyInserters.fromObject("Information"))                         .header("Authorization", login.getSessionId())                         .retrieve()                        .bodyToMono(String.class);              } catch (SSLException e) {                 e.printStackTrace();             }              return  medicationBundles.map(bundles_string -> {                 try {                     List<Object> bundle_list = mapper.readValue(bundles_string, new TypeReference<List<Object>>(){});                     bundle_list.forEach(bundle-> processBundle(bundle,medicationList));                      return medicationList;                 } catch (JsonProcessingException e) {                     e.printStackTrace();                 }                     return null;                 })         })     }  

The Function:

List<String> medicationList = new ArrayList<>();     private void processBundle(Object bundle, List<String> medicationlist) {        //do something to get id from bundle         String ID = bundle.getID(); // if i add something to medicationList.add(ID) here, it is in the required return of my API         Mono<String> Medication =              webClientBuilder.build()             .get()             .uri("URI_API2_GET_DATA"+ID)             .retrieve()             .bodyToMono(String.class);           Medication.map(medication_ID -> {         //do something to get information from medication_ID             String info = medication_ID.getInfo();              //this Information comes after the required return             return medicationList.add(info+ID);         }).subscribe();     } 

My Problem is, that the return comes before the required last map is completed. I somehow missing something. I tried different approaches with e.g. then(), thenMany(), thenReturn() in different positions. Is there a way to do this? If there is a perhaps already a finished simple example, that would also help!

           

Lista de respuestas

2
 
vote
vote
La mejor respuesta
 

Es difícil seguirlo en su código porque está mezclando y coincide con la programación reactiva con la programación imperativa de una manera que no sea la mejor práctica.

Código que no compila y tiene varias cosas extrañas como medID Nunca se está utilizando y las variables nunca se declararon como body . Así que solo he tomado su código "como está", no he producido un ejemplo de trabajo completamente trabajando solo una guía.

Debe elegir para ir a la ruta reactiva, o la ruta imperativa. Algunas partes de mi respuesta serán opinadas, si alguien más tarde se quejara, por lo que es el descargo de responsabilidad.

En primer lugar, usted está en cada solicitud Creación de varios WebClients2 Esto está en mi opinión, se considera mala práctica. Crear un WebClient es una especie de una operación costosa innecesaria, ya que puede reutilizarlos, por lo que debe declarar a sus clientes web durante el inicio y @Autowire ellos en.

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

y luego usándolos por debajo de su clase.

Después de limpiar su código y dividirlo en funciones, con devoluciones adecuadas, espero que esto le dé una idea de cómo lo haría una especie de estructura. Su problema es que no está regresando correctamente de sus funciones, y no está encadenando en las devoluciones. Tan pronto como necesite usar subscribe , por lo general, sabe que ha hecho algo mal.

  @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);     } }    

Escribí esto por la mano libre sin ningún IDE, es más para mostrarle cómo debe estructurar su código, espero que esto le muestre algunas pautas.

Algunos lo hacen y dons en programación reactiva:

  • Evite el uso de bloques de intento / captura, en la programación reactiva, generalmente devuelve un Mono.empty() para ignorar un error o devolver un 9988777669 que contiene una excepción. < / p>

  • Uso body0 Para procesar las cosas de forma asíncrona, body11111 Para procesar síncrono, hay varios otros operadores como body2 y body3 que conserva el orden, explicando estas son una respuesta separada en sí misma.

  • EVITE body4 MÉTODOS Si puede, siempre intente usarla Funciones puras (Evite manipular listas en funciones de vacío, lo hace con los punteros en C ++, no en Java), puede devolver body5 desde una función reactiva encadenando en el body6 < / Código> Operador.

  • Siempre aproveche el sistema de tipo si es posible, intente no serializarse en body7 si es posible, cree una representación de objetos de los datos y serializa en él.

  • casi nunca se suscribe en su solicitud a menos que usted sea el consumidor de los datos. El que inicia la llamada es generalmente el body8 , su aplicación suele ser el body9 , y el cliente de llamadas (web u otro servicio) es el WebClients0 < / Código>. Múltiple WebClients1 suele ser un olor a código.

  • Siempre intente volver y caerse en las devoluciones. Todas las funciones que he escrito arriba devuelve algo y una cadena en las devoluciones, esto es lo que se llama el WebClients2 . Personalmente, siempre comencé cada función con la escritura de la declaración de retorno en la primera fila, entonces comienzo a escribir mis cosas reactivas. Nunca deje un WebClients3 o WebClients4 sin devolverlo y encadenarlo, porque un 9988777662525 WebClients6 nunca obtendrá corre si nadie se suscribe a él.

  • Estructura de su código en funciones, escribir una función es gratuita :)

Algunas buenas lecturas y videos:

Inicio Reactor

documentación oficial de Webflux (Sugiero pasar por la documentación oficial del reactor primero, estos documentos pueden ser difíciles de entender a menos que sepa reactor bastante bueno)

buen video de la primavera, éste se dirige casi todo lo que he escrito arriba. hacer y no en la programación reactiva

Como se dijo antes de que se basara esto, pero con suerte, esto le dará algunas pautas.

 

It's hard to follow along in your code because you are mixing and matching reactive programming with imperativ programming in a non best practice way.

You code doesn't compile and you have several strange things like medID never being used and variables never declared like body. So i have only taken your code "as is", i have not produced a fully working example only a guide.

You should pick to either go the reactive route, or the imperative route. Some parts of my answer will be opinionated, if anyone later will complain, so thats the disclaimer.

First off, you are on each request creating several WebClients this is in my opinion considered bad practice. Creating a WebClient is sort of an expensive unneeded operation since you can reuse them, so you should declare your web clients during startup and @Autowire them in.

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

And then use them by autowire them into your class.

After cleaning up your code and dividing it up into functions, with proper returns i hope this gives you some idea how i would sort of structure it. Your problem is that you are not returning properly from your functions, and you are not chaining on the returns. As soon as you need to use subscribe you usually know you have done something wrong.

@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);     } }  

I wrote this by free hand without any IDE its more to show you how you should structure your code, hope this will show you some guidelines.

Some do's and donts in reactive programming:

  • Avoid using try/catch blocks, in reactive programming you usually either return a Mono.empty() to ignore an error or you return a Mono.error() containing an exception.

  • Use flatMap to process things asynchronously, map to process synchronous, there are several other operators like concatMap and flatMapSequential that preserve order, explaining these are a seperate answer itself.

  • avoid void methods if you can, always try to use pure functions (avoid manipulating lists in void functions, you do that with pointers in C++ not in Java), you can return Mono<Void> from a reactive function by chaining on the .then() operator.

  • Always take advantage of the type system if possible, try not to serialize into Object if possible create an object representation of the data and serialize into it.

  • Almost never subscribe in your application unless you are the consumer of the data. The one that initiates the call is usually the subscriber, your application is usually the producer, and the calling client (web, or other service) is the subscriber. Multiple subscribers is usually a code smell.

  • Always try to return and chain on the returns. All functions i have written above return something and a chain on the returns, this is what is called the construction belt analogy. I personally always start every function with writing the return statement on the first row, then i start writing my reactive stuff. Never leave a Mono or Flux without returning and chaining on to it, because a Mono or Flux will never get run if no one subscribes to it.

  • structure your code into functions, writing a function is free :)

Some good reads and videos:

Getting started Reactor

Webflux official documentation (i suggest going through the official reactor documentation first, these docs can be hard to understand unless you know reactor quite good)

Good video from spring one, this one addresses pretty much everything i have written above. Do's and don't in reactive programming

As said before this is a bit opinion based, but hopefully this will give you some guidelines.

 
 
     
     

Relacionados problema

3  Reactive de arranque de primaveraRegistrationRepositorio no encontrado  ( Spring boot reactiveclientregistrationrepository not found ) 
Medio ambiente: bota de primavera 2.3.1, java 11 Ya he probado algunas cosas (también comparando con la aplicación de muestra por primavera), pero hasta a...

2  No se pueden encontrar anotaciones Javax.persistence cuando use la primavera R2DBC  ( Cannot find javax persistence annotations when using spring r2dbc ) 
Estoy tratando de usar repositorios reactivos con H2 usando Spring Boot . He añadido dependencias implementation 'org.springframework.boot.experimenta...

0  Devolver mono / flujo con 2 suscripciones anidadas  ( Return mono flux with 2 nested subscriptions ) 
Necesito devolver mono / flujo para una función, pero esto tiene 2 suscripciones anidadas. Estoy buscando una mejor solución para publicar mono / flujo solo d...

0  $ u Operador con múltiples expresiones y múltiples campos en una expresión usando los datos de resorte reactiva monogodb  ( Or operator with multiple expressions and multiple fields in an expression usin ) 
En MongoDB, puedo usar $or[{key1:'value11', key2:'value12'}, {key1:'value21', key2:'value22'}, {key1:'value31', key2:'value32'}, ...] para consultar varios ...

0  Respuesta del webclient usando el método Bodytoflux que fusiona toda la respuesta recibida en su lugar que lo separa  ( Webclient response using bodytoflux method merging all the received response ins ) 
Estoy usando paralelflux para ejecutar muchas tareas. Pero cuando estoy recibiendo respuesta a WebClient utilizando el método Bodytoflux, se fusiona tod...

0  Spring WebClient Nested Mono según los códigos de estado  ( Spring webclient nested mono according to status codes ) 
Usando WebClient. Quiero manejar la respuesta de la CLIENTE por separado de acuerdo con los códigos de estado HTTP. A continuación, vea dos nuevos suscríbase ...

1  Configurar manualmente la bota de primavera reactiva reactiva Mongo  ( Manually configure spring boot reactive mongo ) 
Estoy intentando configurar, una aplicación POC de la bota de primavera con múltiples bases de datos. Qué DB debe usarse está determinado por un perfil de res...

0  Función de la nube de primavera y intercambio de letras muertas  ( Spring cloud function and dead letter exchange ) 
Estoy tratando de escribir un servicio de función de nube de primavera reactiva usando RabbitMQ que consumirá una cola y se producirá a un intercambio. Teng...

1  Datos de primavera Plantilla reactiva de Mongo: MONGODB CURSOR  ( Spring data reactive mongo template mongodb cursor ) 
¿Cuál es la forma recomendada de iterar en un resultado grande con la plantilla reactiva de Mongo? No puedo encontrar una alternativa reactiva para la opera...

1  Generando CSV con Spring Reactive Webflux  ( Generating csv with spring reactive webflux ) 
Tengo un punto final que debería generar un informe de CSV con un flujo. Implementé el punto final como este: @GetMapping(value = "/sessions/csv") ...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos