Reactive Programming With Spring 5
In recent times, Reactive Programming has gained popularity among the developer community and customers due to its ability to build applications in a declarative way, as opposed to imperatively, resulting in more responsive and resilient applications. The fact that Spring 5 has incorporated Reactive Systems into its core framework has shown the paradigm shift toward declarative programming.
Reactive Programming manages asynchronous data flows between producers of data and consumers that need to react to that data in a non-blocking manner. So, Reactive Programming is all about non-blocking applications that are asynchronous and event-driven and require a small number of threads to scale.
Reactive applications are difficult to build with thread-based frameworks, as there is high complexity involved in scaling out an application based on shared mutable state, threads, and locks.
In a Reactive Programming context, “Everything is a Stream and acts in a non-blocking manner when there is data in the stream.”
Why Reactive Programming
The high level of abstraction with Reactive Programming leads to an increased readability of code so developers can focus primarily on the interdependence of events that define the business logic.
Reactive patterns fit naturally with message processing in highly concurrent environments, which is a common enterprise use case.
With the feature to enforce backpressure, the Reactive approach is the best fit to control the flow of traffic between producer and consumer, and this will help in avoiding out-of-memory problems.
With one or fewer threads, IO bound tasks can be performed in an asynchronous and non-blocking fashion without blocking the current thread.
Reactive Programming can manage situations more efficiently where highly interactive and real-time applications, or any action/event, may trigger a notification for multiple connected subsystems.
Ideal Use Cases for Implementation of Reactive Programming
A large number of transaction-processing services, as in the banking sector.
Notification services of large online shopping applications, like Amazon.
A share trading business where share prices change simultaneously.
Reactive Streams
"Reactive Streams" defines an API specification that contains a minimal set of interfaces that expose ways to define operations and entities for asynchronous streams of data with non-blocking backpressure.
With the introduction of backpressure, Reactive Streams allows the subscriber to control the data exchange rate from publishers.
The Reactive Streams API is officially part of Java 9 as java.util.concurrent.Flow.
Reactive Streams are used mainly as an interoperability layer.
Spring 5 Offerings for Reactive Programming
Both the Spring-Web-Reactive module and Spring MVC support the same @Controller programming, but Spring-Web-Reactive additionally executes on a Reactive and non-blocking engine.
The Spring-Web-Reactive module and Spring MVC share many common algorithms, but the Spring-Web-Reactive module has redefined many of the Spring MVC contracts, such as HandlerMapping and HandlerAdapter, to make them asynchronous and non-blocking as well as enabling reactive HTTP request and response (in the form of RouterFunction and HandlerFunction).
The new reactive WebClient has also been introduced in Spring 5 in addition to the existing RestTemplate.
HTTP clients (e.g. Reactor, Netty, Undertow) that support Reactive Programming have adapted to a set of reactive ClientHttpRequest and ClientHttpResponse abstractions that expose the request and response body as Flux<DataBuffer> with full backpressure support on the read and write side.
Spring 5 Framework introduced Reactor as an implementation for the Reactive Streams specification.
Reactor is a next-gen Reactive library for building non-blocking applications on the JVM.
Reactor extends the basic Reactive Streams Publisher contract and defines the Flux and Mono API types to provide declarative operations on data sequences of 0..N and 0..1 respectively.
Spring Web Reactive makes use of the Servlet 3.1 offering for non-blocking I/O and runs on Servlet 3.1 containers.
Spring WebFlux provides a choice of two programming models.
Annotated controllers: These are the same as Spring MVC with some additional annotations provided by the Spring-Web module. Both Spring MVC and WebFlux controller support Reactive return types. In addition, WebFlux also supports Reactive @RequestBody arguments.
Functional Programming model: A lambda-based, lightweight, small library that exposes utilities to route and handles requests.
Spring Web Reactive vs. Spring Web MVC
Spring 5 accommodates both Spring Web Reactive (under the spring-web-reactive module) and Spring Web MVC (under the spring-webmvc module) next to each other.
Although both the Spring Web Reactive and Spring Web MVC modules share many algorithms, they do not share code due to the ability of Spring Web Reactive to run on a Reactive Streams HTTP adapter layer that is Reactive and non-blocking.
Spring MVC execution requires Servlet containers while Spring Web Reactive also runs on non-Servlet runtimes, such as on Netty and Undertow.
Switching from a Spring MVC application to Spring Web Reactive should be considered if there is an absolute requirement for a non-blocking web stack with a lightweight, functional web framework for use with Java 8 lambdas or Kotlin.
Basic Configuration for Reactive Programming
Here is pom.xml with the M5 version of 2.0.0 and a dependency for WebFlux.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId> </dependency>
</dependencies>
Traditional Approach vs. Reactive Approach
In a traditional approach, execution will be blocked and will wait until the completion of your service execution. In the following code, after the first print statement, program execution will be blocked and waiting for service execution to complete. After the completion of service execution, the program execution will be resumed and the second print statement will be executed.
@GetMapping("/traditional")
public List < Product > getAllProducts() {
System.out.println("Traditional way started");
List < Product > products = prodService.getProducts("traditional");
System.out.println("Traditional way completed");
return products;
}
In a Reactive approach, program execution will continue without waiting for the completion of service execution. In the following code, after the first print statement, the second print statement will be executed in a non-blocking manner without waiting for the completion of service execution. The Flux stream will be populated with the availability of product data.
@GetMapping(value = "/reactive", .TEXT_EVENT_STREAM_VALUE)
public Flux < Product > getAll() {
System.out.println("Reactive way using Flux started");
Flux < Product > fluxProducts = prodService.getProductsStream("Flux");
System.out.println("Reactive way using Flux completed");
return fluxProducts;
}
Reactive Web Client
Spring 5 introduced the Reactive WebClient in addition to the existing RestTemplate.
The ClientHttpRequest and ClientHttpResponse abstractions expose the request and response body as Flux<DataBuffer> with full backpressure support on the read and the write side.
The Encoder and Decoder abstractions from Spring Core are also used on the client side for the serialization of a Flux of bytes to and from typed objects.
Below is a sample of a Reactive WebClient that calls the endpoint and receives and handles the Reactive Stream Flux object.
@GetMapping("/accounts/{id}/alerts")
public Flux < Alert > getAccountAlerts(@PathVariable Long id) {
WebClient webClient = new WebClient(new ReactorClientHttpConnector());
return this.repository.getAccount(id).flatMap(account -> webClient.perform(get("/alerts/{key}", account.getKey())).extract(bodyStream(Alert.class)));
}
Limitations of Spring 5
Troubleshooting a Reactive application is a bit difficult, and there is the possibility that, while fixing an issue, you might accidentally block code has been introduced.
Most of the traditional Java-based integration libraries are still blocking.
Limited options are available for Reactive data stores, except for a few NoSQL databases such as MongoDB.
Spring Security is still not supported.