Reactive Programming with Spring Boot 2

TLDR;

In this article that’s the fourth part of a running series on our Tech Journey at Itembase, we will have a look at the new Spring Webflux framework, talking about the differences from its predecessor Spring MVC and highlighting the new Reactive Programming model that it introduces, trying to understand what this means for us and what are its advantages, ending with a number of real world examples on what can be done with the framework.

 

The Legacy Way: Spring MVC

The Spring MVC framework has been the most beloved product from the Spring ecosystem, bringing together the powerful features of Core Spring (such as IoC) and the clever abstractions of the web framework designed to run on any Servlet Container (such as Wildfly and Tomcat), offering a very flexible yet complete framework to the developer.

The fact that Spring MVC has been a solid alternative to the J2EE stack in these past years speaks volumes about its powerful abstractions and extreme completeness in terms of functionalities provided right out of the box, and when these are not enough, then it’s always possible to add complementary modules (such as Spring Security) that fit exactly the right way into the framework programming model.

One of the many advantages introduced by Spring MVC and actually provided by the Spring Core framework, was the configurability of the whole application via specialized JavaBeans annotated with @Configuration.

These simple POJOs allowed the developer to define all the beans necessary for its application, and, more importantly, allowed him to customize the behaviour of the framework by providing specialized beans that fulfil a particular role.

For example, if the default DispatchServlet is not acceptable for a given application, it’s possible to replace it with one defined by the developer, in the same way that’s possible to configure the underlying servlet container if there’s a specific need.

Furthermore, the integrated support for different template engines (like JSP, Velocity and Thymeleaf) makes it easy to define REST APIs that also offer some views to the browser if necessary.

The support for validation of incoming requests and their payloads is also a very useful feature, because it frees our code from doing this manually, removing the possibility of error (there’s always a non-zero chance that a human makes mistakes when writing code) and allowing our code to process requests that are always valid, with the benefit that we will not code for any edge case in case the request is invalid, because the framework will take care of handling the invalid ones for us.

Another useful feature is the possibility to use specialized annotations to control requests’ behaviour, such as what kind of body to expect (via @RequestBody), providing strong guarantees that the type of the request payload will always be strictly checked by the compiler and by the framework at runtime.

Declaring query parameters is a simple as using the @RequestParam annotation, and using path parameters is also quite easy with the @PathParam annotation.

The Spring MVC framework has been a solid choice for a long time and it still is when the project does not require the use of Reactive Programming.

By its nature, the Servlet Container used by the Spring MVC uses the blocking model when handling requests, which makes it incompatible with the Reactive Programming model.

This brings us to the next point, the new web framework called Spring Webflux, which embraces the Reactive Programming model and makes it convenient to write reactive, non-blocking asynchronous code.

 

Spring Webflux

Spring Webflux is the new web framework introduced by Spring 5 (docs) and incorporated as well in Spring Boot 2.

It’s a reactive, non-blocking asynchronous web framework that brings the new Reactive Programming model in the web programming space, combining it with the established power and tools of the well-known Spring Core framework.

Spring Webflux was born to address the need for a non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources, leveraging an established JVM server such as Netty that’s already asynchronous and non-blocking.

Reactive Streams (https://www.reactive-streams.org/) and Project Reactor (https://projectreactor.io/) are the foundations of the web framework, that builds on top of these existing libraries/concepts and provides higher-level abstractions, allowing the developer to focus on the business logic and leave as much as possible the boilerplate to the framework itself.

Like it was for Spring MVC, also in Spring Webflux it’s possible to configure practically every aspect of the framework, tuning its parameters as it’s needed (e.g. max request size, max number of threads, and so on) and when the situation calls for it, replacing entire parts of the framework itself by providing a specialized object that fulfils the same role.

One key difference right out of the box is that Spring WebFlux provides a choice of two programming models:

  1. Annotated Controllers (https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-controller) exactly like it was for Spring MVC, with the support for the classic annotations such as @Controller and @RestController
  2. Functional Endpoints (https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-controller) that is a lambda-based, lightweight way of defining functions that handle routes and requests.

The big difference with annotated controllers is that the application is in charge of request handling from start to finish versus declaring intent through annotations and being called back.

It is possible to mix both approaches in a single application, though it’s not encouraged because of the inconsistency that would be introduced in the code base.

Spring MVC relies on Servlet blocking I/O and lets applications use the Servlet API directly if they need to. Spring WebFlux relies on Servlet 3.1 non-blocking I/O and uses the Servlet API behind a low-level adapter.

Spring Webflux introduces a new webclient that supersedes the Spring MVC client RestTemplate, with a new reactive interface and a non-blocking operation mode.

The new webclient, aptly named WebClient, included in this new release of Spring is a very convenient abstraction to perform HTTP requests to external resources and return an object of a specific type wrapped into a functional Reactive Stream such as Mono or Flux, depending on how many results are contained in the response.

The above mentioned features are only some of the many improvements that the Spring Webflux brings to developers, with some others that we will be looking at more closely in the following sections.

 

Advantages of Spring Webflux

What’s the point of adopting the new web framework included in Spring Webflux?

That’s a legit question, and the answer is more than a simple list of advantages, it’s a rich mix of reasons that involve the way the framework functions and the structure of the existing application.

The main reason is undoubtedly the performance gains that are introduced in this release, related to the non-blocking nature of the underlying server.

The Reactive Programming model introduced lets the developer reap the benefits of the framework without burdening him too much with the low-level details of the request handling.

The framework is configurable in almost every aspect, and thus it’s ideal for a wide range of applications, due to this flexibility.

Another reason for adopting the framework is surely the consistency in the reactive stack.
If we already use Spring Core/Spring Boot 2+ in our application, Webflux is already included and it’s configured to work very well with the existing Spring stack, allowing us to reuse code and reduce the amount of “glue code” needed to integrate it with a pre-existing codebase.

Furthermore, on a related note about performance, the predictability of the scaling behaviour under load is definitely a solid reason to add to the list, because the expected benefit of reactive and non-blocking is the ability to scale with a small, fixed number of threads and less memory, which leads to more graceful handling of loads and, combined with backpressure (where applicable), makes for a very resilient system.

Scaling with a small number of threads may sound contradictory, but if you never block the current thread (and rely on callbacks instead) there is no need for extra threads, as there are no blocking calls to absorb.

 

Disadvantages of Spring Webflux

It’s not all fun and games with Webflux, there are certain use cases where it does not make sense to use it, or where its use brings more problems than it solves.

One of these cases is where you have a Spring MVC application that works fine, then there is no need to change. Imperative programming is the easiest way to write, understand, and debug code. You have maximum choice of libraries, since, historically, most are blocking.

There is no gain to obtain if you change just for the sake of changing.

On the contrary, the shift to functional, reactive and non-blocking way of writing code will probably cause you and your team some pain if the change is unjustified and has not been well planned.

One of the biggest disadvantages of the Webflux is that it is reactive, a fact that brings a lot of additional complexity and needs time to be thoroughly understood and appreciated.

Another drawback is the different debugging experience. Errors in the Reactive Pipelines are often buried in stacktraces hundreds of lines long, and it might be hard to find the exact issue as most of the lines point to the Project Reactor library instead of your code.

Some experience is required when debugging production issues or even when looking at stacktraces/logs of async and reactive code, because the non-linearity pattern of code execution.

Adding to the pile of minor to mild issues there’s the fact that many well-known libraries work in a blocking manner. Very often, those which use non-blocking paths aren’t sufficiently mature. Hopefully, this will change soon given the effort in this space.

 

Examples

As we have seen in the previous sections, Webflux comes with a lot of new components included and promises performance gains when there are use cases for blocking/waiting situations.

Let’s now have a look at what is like writing a Webflux application, what are the main components and how we can organize our code to make it future-proof and have it always ready to change.

Defining the main components is the first step, as they are the core of the application.

Below is a typical way of organizing a Webflux application, assuming we adopt the “Functional Endpoint” way:

  • One or more Handlers, POJOs that have the responsibility of receiving the request, performing the required business logic and returning an appropriate response.
  • At least one Router, a POJO that links together endpoints (URLs) and handlers, specifying the type of request (accepted content type, returned content type, streaming or not, chunked, and so on), and of course the supported HTTP method.

The Router will also specify any path/query/body parameter that’s expected in the request

  • A WebClient that is used to talk to the external world (fetching resources from other external endpoints).
    Usually the webclient is not used as it is, but it’s “wrapped” into a specialized class that takes care of exposing the higher-level methods needed to the business logic, leaving all the details of request/response marshalling an unmarshalling to the given implementation.

An example might be interacting with the Amazon API, where there are a number of pre-steps to follow before a request can be made (that are always the same for every request) and a number of transformations needed when the response is received.

This logic of preparing the request and handling the response can be centralized into, for example, an AmazonWebClient, a specialized version of the bare webclient that only exposes the methods that are sufficiently high level to be useful to the business layer.

  • A Data Layer that talks to a Database (even if this is not strictly related to Webflux, it’s a matter of fact that most applications interact with a datastore, relational or not).
  • An optional Presentation Layer that might display some views (likely rendered HTML from a template).

This might not be there if the application is purely REST and has no user-visible page, but there are cases where the APIs are augmented with some helper pages for the user/developer and thus have the need to render a HTML page to return to a browser.

  • There might also be a requirement for ACLs or any form of policy on what kind of resource a given account can access, and thus the Spring Security 5 module can be easily integrated with our Webflux application.

This list is not comprehensive given that we might have additional components or even remove some because they are not needed for a given use case.

In general, this is a good starting point, and it’s the one we are going to have a look at in our following example.

Here is the GitHub link with the example code:

https://gist.github.com/Francesco-itembase/b1f8b84889937a5d7d46af7c1ff8350e

 

Conclusions

Spring Webflux is a very solid and battle-tested framework, that introduces the world of Reactive programming into the server-side development of REST APIs for Java, coupled with the beloved Spring Core, that enable the developer to reap performance gains without having to dive too deep into the whole async/non-blocking world, but instead, using the provided Mono/Flux (and their operators) abstractions.

Here at Itembase, the use of Webflux enabled us to scale while keeping our resource consumption low, and, more importantly, allowing us to serve our customers exactly as fast as they could accept our data flow, which means that we will not overload them in case we receive a flood of data from a commerce system.

This has been a high-level overview of the Webflux framework, please check our first article for a more in-deep analysis of the Mono/Flux Reactive Streams, and stay tuned for the next article on how to debug these Reactive applications.

 

<< Reactive Programming: Performance & Trade-Offs