简体   繁体   中英

Spring Boot Webflux - flatMap is the correct way to chain http calls?

Small question regarding Spring Webflux, and how to "chain" http calls please.

With a concrete example, here is a very straightforward sample with Spring MVC, with a rest template.

RestTemplate restTemplate = new RestTemplate();

        HttpEntity<StepOneRequest> stepOneRequestHttpEntity = new HttpEntity<>(new StepOneRequest("foo", "bar"));
        StepOneResponse stepOneResponse = restTemplate.postForObject("https://step-one-web-service:443/getStepTwoFromFooBar", stepOneRequestHttpEntity, StepOneResponse.class);

        HttpEntity<StepTwoRequest> stepTwoRequestHttpEntity = new HttpEntity<>(new StepTwoRequest(stepOneResponse.getTheDependencyFromStepOne()));
        StepTwoResponse stepTwoResponse = restTemplate.postForObject("https://step-two-web-service:443/getResponseFromStepTwo", stepTwoRequestHttpEntity, StepTwoResponse);

        return ResponseEntity.ok(new MyResponse(stepTwoResponse.getImportantValueFromStepTwo()));
    }

In this snippet, we see very straightforward. Initialisation of only one rest template.

Construction of a http request payload object.

Use the constructed object to query a first external web application API to get a response.

Important, the response of the first HTTP call is needed to make the second HTTP call. They can only be sequential, it needs the result of the first call, to be able to query the second API.

Then, the http call to the second API.

Finally, response based on the second API to the original requester.

Now, if this is translated to Webflux:

WebClient webClientStepOne = WebClient.create("https://step-one-web-service:443/getStepTwoFromFooBar");
        WebClient webClientStepTwo = WebClient.create("https://step-two-web-service:443/getResponseFromStepTwo");

        Mono<StepOneResponse> stepOneResponseMono = webClientStepOne.post().body(BodyInserters.fromValue(new StepOneRequest("foo", "bar"))).retrieve().bodyToMono(StepOneResponse.class);

        Mono<StepTwoResponse> stepTwoResponseMono = stepOneResponseMono.flatMap(aStepOneResponse -> webClientStepTwo.post().body(BodyInserters.fromValue(aStepOneResponse.getTheDependencyFromStepOne())).retrieve().bodyToMono(StepTwoResponse.class));

        return stepTwoResponseMono.map(aStepTwoResponse -> ResponseEntity.ok(new MyResponse(aStepTwoResponse.getImportantValueFromStepTwo())));
    }

I have one big question here. While this is correct (it yields the same response) is this the correct thing to do?

Especially, the line with the flatMap

Mono<StepTwoResponse> stepTwoResponseMono = stepOneResponseMono.flatMap(aStepOneResponse -> webClientStepTwo.post().body(BodyInserters.fromValue(aStepOneResponse.getTheDependencyFromStepOne())).retrieve().bodyToMono(StepTwoResponse.class));

It seems very out of the picture. Is there a better way here?

Also, a side question, do we really need N WebClient if I need to chain N api calls please? The URLs are different, not just the path, the complete URL. When rest template can do with only one instance, it seems here, I need N WebClient if I need to call N external services.

What is the correct way to use WebClient here please? What is the correct way to chain http calls please?

Thank you

I have one big question here. While this is correct (it yields the same response) is this the correct thing to do?

Essentially, yes, you've done it correctly. The only things that aren't really correct there are your Webclient usage, and your style.

You certainly don't have to specify a WebClient per URI (and you shouldn't, they're meant to be reusable.) So if you have different domains and the same webclient, just create a standard instance:

WebClient wc = WebClient.create();

...and then use:

webClientStepOne.post()
                .uri("https://blah")

...at the top of your chains to specify a different URI with the same instance.

In terms of style - you'll have noticed the code gets unwieldy very quickly when split up into separate variables and written on long lines. Counterintuitive as it might first seem, the generally accepted best practice in reactive land is to do everything in a single statement, formatting each new method call on a separate line (enabling you to read down and then see the execution of your reactive chain.) So in your case, it would become:

return wc.post().uri("https://step-one-web-service:443/getStepTwoFromFooBar")
        .body(BodyInserters.fromValue(new StepOneRequest("foo", "bar")))
        .retrieve()
        .bodyToMono(StepOneResponse.class)
        .flatMap(aStepOneResponse ->
                wc.post().uri("https://step-two-web-service:443/getResponseFromStepTwo")
                        .body(BodyInserters.fromValue(aStepOneResponse.getTheDependencyFromStepOne()))
                        .retrieve()
                        .bodyToMono(StepTwoResponse.class)
        )
        .map(aStepTwoResponse ->
                ResponseEntity.ok(new MyResponse(aStepTwoResponse.getImportantValueFromStepTwo()))
        );

You could tidy it up a bit maybe, for instance by delegating the inner flatmap call to a separate method - but that really just comes down to preference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM