Reactive Java: combining Mono(s)

Reactive java combining Mono(s)
Reading Time: 4 minutes

Hi all, as the title Reactive Java: Combining Mono(s) suggests, here we are going to use spring boot web flux to combine multiple Mono(s) into a single operation using the zipWith() method.

We focus a lot on non-blocking and asynchronous communication in a reactive micro-service architecture. Spring boot has provided the support for the same. Spring’s support for Reactive micro-service is based on the MONO and FLUX API’s at its core. What exactly does it mean to be reactive? The answer is the ability to react to a change almost immediately, like rendering of UI components on mouse click/hover actions.

The example of mouse clicks/hovering on UI is quite straight forward. One very important thing to consider with non-blocking reactive applications is back pressure. We have to provide ways to handle back pressure. In a non-blocking environment, it becomes important to control the rate of events so that a fast producer does not overwhelm its destination. This is where reactive streams comes into picture. Reactive streams defines the interaction between the asynchronous components with back pressure. The main purpose of reactive streams is to let the subscriber control how fast or slow a publisher produces data. Spring web flux uses project reactor at its core for providing the support of reactive streams. It provides the mono and flux API types to work on data sequences 0..1 and 1..n respectively.

In this post we will focus on:

1. How we combine multiple mono’s together?

2. What could go wrong if we do not consider the basics?

Scenario

Let us go through the following code snippets:

In the snippet’s above we have defined a simple `BlogController.java` class. This gets you the blog details and its ratings. User has to provide a blog id in the request to get the details and blog rating. For the sake of simplicity we will use a static Map<Integer, String> as our backend data source. The code is quite simple. We inject a repository instance to our controller class, and then invoke the repository methods. We combine the result obtained from the 2 repository calls using the Mono.zipWith() function. Ideally, if our static map contains blog Id we should be able to see the blog detail and its rating. Now, let’s run the service and try to hit the endpoint.

Successful response after combining two mono's.

We can see that the response includes the blog name and rating for that blog. We were able to combine two mono’s into one and manipulate their response. So far so good, but Wait!! Aren’t we missing something? What happens if the blog id we send in the URL does not exist in our map? Will the second mono fetch the blog rating, if the first mono results empty? Will second repository call execute? Let’s see…

Empty Mono

Response is successful but internal calls did not work as per the expectations.
When we pass blog id = 20, which obviously does not exist in the map, we get an empty response from the service. This is not the expected behavior for any well written service/application. Instead, service should provide an error or a detailed message that the requested id was not found. What went wrong here?
Basics!! Yes, we forgot the basic of using the zipWith() method of Mono API. As per the documentation for the zipWith() method:
It Combines the result from this mono and another into a Tuple2.
An error or empty completion of any source will cause the other source
to be cancelled and the resulting Mono to immediately error or complete, respectively.


We forgot to capture the state when our repository call would return an empty Mono. That is why we see that when first repository call returned Mono.empty the second repository call was never made/processed. The program execution was simply dropped thereafter, and empty response was sent back immediately to the user.

Solution:

The easiest way to solve such a problem is to provide a switchIfEmpty() clause to your Mono calls. When our backend/3rd-party call results in a Mono.empty() we should not by pass the program execution. We should continue to execute the rest of the code statements. We should not halt the application processing. Therefore, we should be careful when combining the mono execution and should provide a fallback mechanism. Whenever the Mono results in an empty state the switchIfEmpty() method acts as a fallback. We can update the BlogController.java class to use the switchIfEmpty(). Let’s see how that would help, see the updated controller below:

Now that we have added the switchIfEmpty() to the mono call this would provide a fallback when any mono results in empty. So there won’t be a scenario that when the first repo call reslut in Mono.empty(), second call won’t get executed. This way we can make sure that both the repository calls will execute. Let’s confirm that by hitting the same endpoint for an invalid id:

Ideal result if a mono returns empty when used in the .zipWith() function. Thats how we should use spring-boot to combine mono(s).

It is clear from the above snapshot, both the repository calls execute and our fallback works as expected. That is how we improve the usage of zipWith() for mono chaining.

I hope you liked the blog, in a way that it would encourage you to code on your own and find other hidden aspects of Mono chaining in Spring. If you have any query regarding the topic please feel free to ask that in the comments section below. Any suggestions or improvements are more than welcome. As a result, i would be able to come up with a better one in the future. Happy coding. 🙂

References:

  1. https://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html
  2. https://spring.io/
Knoldus-blog-footer-image

Written by 

Prashant is a Senior Software Consultant having experience of more than 3 years, both in service development and client interaction. He is familiar with Object Oriented Programming Paradigms and has worked upon Java and Scala-based technologies and has experience working with the reactive technology stack, following the agile methodology. He's currently involved in creating reactive microservices some of which are already live and running successfully in production, he has also worked upon scaling of these microservices by implementing the best practices of APIGEE-Edge. He is a good team player, and currently managing a team of 4 people. He's always eager to learn new and advance concepts in order to expand his horizon and apply them in project development with his skills. His hobbies include Teaching, Playing Sports, Cooking, watching Sci-Fi movies and traveling with friends.