Illustrating how going from async to sync can be easy, but the opposite is not

Invariably, the question comes along--Do I need a synchronous or asynchronous API?

It's important to understand that reactive programming is not very effective unless the entire stack is reactive. Otherwise, we're simply blocking at some point, which causes the backpressure to not achieve much. That's a long-winded way of saying there is little value in making the web layer reactive if the underlying services are not.

However, it is very likely that we may produce a chunk of code that must be tapped by a non-reactive layer, hence, we have to wrap our asynchronous, non-blocking code with the means to block.

Let's explore async-to-sync by creating a BlockingImageService. This service will, basically, leverage the already written ImageService, but not include any of Reactor's Flux or Mono types in its method signatures.

We can start with a class definition as follows:

    public class BlockingImageService { 
 
      private final ImageService imageService; 
 
      public BlockingImageService(ImageService imageService) { 
        this.imageService = imageService; 
      } 

This preceding class definition can be described as follows:

  • The class has no annotation, hence, it won't be automatically scanned and activated by Spring Boot. However, it can appear in a configuration class somewhere via a @Bean-annotated method.
  • It will contain a constructor injected ImageService.

With this in place, we can look at wrapping the findAllImages() method with blocking semantics, like this:

    public List<Image> findAllImages() { 
      return imageService.findAllImages() 
       .collectList() 
       .block(Duration.ofSeconds(10)); 
    } 

Let's dig into the details of the last code:

  • ImageService.findAllImages() has no arguments, and returns a Flux<Image>. The simplest mechanism is collectList(), which transforms it into a Mono<List<Image>>. This means that instead of signaling the arrival of each image, there is one single (Mono) for a list of ALL images.
  • To ask for the result, we use block(). Reactor's block() can either wait forever for the next signal, or we can supply it with a timeout limit. In this case, we have selected ten seconds as the longest that we'll wait.

Reactor's block() API is what we do when we want to transform a Mono<T> into just T. It's a simple one-to-one concept. Inside the method, it invokes the reactive streams' subscribe() API, meaning it will cause any chain of operations to take effect.

Flux has no block() because it represents multiple values. Flux does come with blockFirst() and blockLast() if we wanted the first or the last item. But to get the whole collection entails a bigger semantic scope. Hence, the need to collectList() into a Mono followed by blocking for it.

It's usually a good idea to set a timeout limit for any async call to avoid deadlock situations or waiting for a response that may never come.

Fetching a single image is a bit simpler and can be done using the following code:

    public Resource findOneImage(String filename) { 
      return imageService.findOneImage(filename) 
       .block(Duration.ofSeconds(30)); 
    } 

ImageService.findOneImage() has one argument, the filename, but it isn't wrapped with any Reactor types. The return type is Mono<Resource>, so a simple block() is all we need to transform it into a Resource. In this case, we've picked thirty seconds as the maximum time to wait for an answer.

When it comes to uploading new images, that is a little more complicated.

    public void createImage(List<FilePart> files) { 
      imageService.createImage(Flux.fromIterable(files)) 
       .block(Duration.ofMinutes(1)); 
    } 

The last code can be described as follows:

  • The image service's input is Flux<FilePart> and the return type is Mono<Void>. This makes things doubly interesting, having to massage both the input and the output.
  • The preceding code assumes we are uploading multiple files. To transform it into a Flux, we use Flux.fromIterable(files). If the input had been a single FilePart, we could have used Flux.just(file).
  • The return type is void, meaning we don't have to return anything. Simply invoking image service's create() method may seem hunky dory. But remember--nothing happens with Reactor types until we subscribe, so it's critical that we invoke block() even if we aren't going to return it.

We'll leave it as an exercise for the reader to implement a blocking version of deleteImage().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.163.250