How should one do sync http requests in modern Spring?
Asked Answered
R

1

8

For a long time, Spring has been recommending RestTemplate for sync http requests. However, nowadays the documentation says:

NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the org.springframework.web.reactive.client.WebClient which has a more modern API and supports sync, async, and streaming scenarios.

But I haven't been able to see how one is recommended to use WebClient for sync scenarios. There is this in the documentation:

WebClient can be used in synchronous style by blocking at the end for the result

and I've seen some codebases using .block() all over the place. However, my problem with this is that with some experience in reactive frameworks, I've grown to understand that blocking a reactive call is a code smell and should really be used in testing only. For example this page says

Sometimes you can only migrate part of your code to be reactive, and you need to reuse reactive sequences in more imperative code.

Thus if you need to block until the value from a Mono is available, use Mono#block() method. It will throw an Exception if the onError event is triggered.

Note that you should avoid this by favoring having reactive code end-to-end, as much as possible. You MUST avoid this at all cost in the middle of other reactive code, as this has the potential to lock your whole reactive pipeline.

So is there something I've missed that avoids block()s but allows you to do sync calls, or is using block() everywhere really the way?

Or is the intent of WebClient API to imply that one just shouldn't do blocking anywhere in your codebase anymore? As WebClient seems to be the only alternative for future http calls offered by Spring, is the only viable choice in the future to use non-blocking calls throughout your codebase, and change the rest of the codebase to accommodate that?

There's a related question here but it focuses on the occurring exception only, whereas I would be interested to hear what should be the approach in general.

Roundy answered 9/9, 2021 at 6:9 Comment(2)
I would like to ask an un-popular question please. We had the same dilemma, for quite a while; replace RestTemplate with something. We have settled with jdks native client. It's not easy, at all (no interceptors, a complicated API to get used to, bare minimum functionality that you need to extend, etc), but why use anything else and not the JDK client?Hominy
@Hominy you refer to HttpClient that came with java 11? I don't think they are mutually exclusive - after all, Springs client is just an abstraction where HttpClient is an implementation, and you can have both. However, if one is already using Spring, isn't using non-spring apis for this cumbersome?Roundy
M
1

Firstly accoring to the WebClient Java docs

public interface WebClient Non-blocking, reactive client to perform HTTP requests, exposing a fluent, reactive API over underlying HTTP client libraries such as Reactor Netty. Use static factory methods create() or create(String), or builder() to prepare an instance.

So webClient is not created to be blocking somehow.

However the response that webClient returns can be of type <T> reactor.core.publisher.Flux<T> and other times of type <T> reactor.core.publisher.Mono<T>. Flux and Mono from reactor project are the ones that have blocking methods. ResponseSpec from WebClient.

WebClient was designed to be a reactive client.

As you might have seen from other reactive libraries from other languages example RxJs for javascript the reactive programming is usually based on functional programming.

What happens here with Flux and Mono from reactor project is that they allow you to make block() in order to make synchronous execution without the need of functional programming.

Here is a part of an article that I find much interesting

Extractors: The Subscribers from the Dark Side

There is another way to subscribe to a sequence, which is to call Mono.block() or Mono.toFuture() or Flux.toStream() (these are the "extractor" methods — they get you out of the Reactive types into a less flexible, blocking abstraction). Flux also has converters collectList() and collectMap() that convert from Flux to Mono. They don’t actually subscribe to the sequence, but they do throw away any control you might have had over the suscription at the level of the individual items.

Warning A good rule of thumb is "never call an extractor". There are some exceptions (otherwise the methods would not exist). One notable exception is in tests because it’s useful to be able to block to allow results to accumulate. These methods are there as an escape hatch to bridge from Reactive to blocking; if you need to adapt to a legacy API, for instance Spring MVC. When you call Mono.block() you throw away all the benefits of the Reactive Streams

So can you do synchronous programming without using the block() operations ?

Yes you can but then you have to think in terms of functional programming for your application.

Example

   public void doSomething1( ) {
      webClientCall_1....subscribe( response1 -> {

          ...do something else ...
          webClientCall_2....subscribe( response2 -> {
              ...do something else more with response1 and response2 available here...
            });
       });
   }

This is called subscribe callback hell. You can avoid it using .block() methods but again as the provided article mentioned they throw away the reactive nature of that library.

Multifoil answered 9/9, 2021 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.