Following previous Reactive Spring and Reactor Core 3.0 blog posts, I would like to explain why Reactive types are useful and how they compare to other asynchronous types, based on what we have learned while working on the Spring Framework 5 upcoming Reactive support.
Reactive types are not intended to allow you to process your requests or data faster, in fact they will introduce a small overhead compared to regular blocking processing. Their strength lies in their capacity to serve more request concurrently, and to handle operations with latency, such as requesting data from a remote server, more efficiently. They allow you to provide a better quality of service and a predictable capacity planning by dealing natively with time and latency without consuming more resources. Unlike traditional processing that blocks the current thread while waiting a result, a Reactive API that waits costs nothing, requests only the amount of data it is able to process and bring new capabilities since it deals with stream of data, not only with individual elements one by one.
Before Java 8, asynchronous non-blocking behavior was not obvious to implement for at least two reasons. The first reason is that callback based API required verbose anonymous classes and are not easy to chain. The second reason is that
Future type is asynchronous but blocks the current thread until the computation completes when you try to get the result with the
get() method. That’s why Spring Framework 4.0 introduced
Future implementation that adds non-blocking callback-based capabilities.
Then Java 8 introduced lambdas and
CompletableFuture. Lambdas allow to write concise callbacks, while
CompletionStage interface and
CompletableFuture class finally allows to deal with future in a non-blocking way and push-based fashion, while providing capabilities to chain such deferred result processing.
Java 8 also introduced
Stream, which has been designed to deal efficiently with stream of data (including primitive types) that can be accessed with no or very little latency. It is pull-based, can only be used once, lacks time-related operations and can perform parallel computations but without being able to specify the thread pool to use. As explained by Brian Goetz,
it has not been designed to deal with operation with latency, such as I/O operations. And that is where Reactive APIs like Reactor or RxJava come in.
Reactive APIs such as Reactor also provide operators like Java 8 Stream, but they work more generally with any stream sequence (not just Collections) and allow to define a pipeline of transforming operations that will apply to the data passing through it thanks to a handy fluent API and using lambdas. They are designed to handle both synchronous or asynchronous operations, and allow you to buffer, merge, concatenate, or apply a wide range of transformations to your data.
Initially Reactive APIs were only designed to deal with streams of data, i.e. N elements, for example, using Reactor’s
reactiveService.getResults() .mergeWith(Flux.interval(100)) .doOnNext(serviceA::someObserver) .map(d -> d * 2) .take(3) .onErrorResumeWith(errorHandler::fallback) .doAfterTerminate(serviceM::incrementTerminate) .consume(System.out::println);
But during our work on Spring Framework 5, it became apparent that there was a clear need to distinguish between streams of 1 or N elements, and that is why Reactor provides the
Mono is the Reactive equivalent of
CompletableFuture type, and allow to provide a consistent API for handling single and multiple elements in a Reactive way.
Mono.any(reactiveServiceA.findRecent(time), reactiveServiceB.findRecent(time) .timeout(Duration.ofSeconds(3), errorHandler::fallback) .doOnSuccess(r -> reactiveServiceC.incrementSuccess()) .consume(System.out::println);
Reactor is built on the Reactive Streams specification. Reactive Streams is composed of 4 simple Java interfaces (
Processor), a textual specification and a TCK. It is the cornerstone of every modern Reactive library and a must have for interoperability purpose.
The core concern of Reactive Streams is handling backpressure. In a nutshell, backpressure is a mechanism that permits a receiver to ask how much data it wants to receive from the emitter. It allows:
- The receiver to start receiving data only when it is ready to process it
- To control the inflight amount of data
- Efficient handling of slow emitter/fast receiver or fast emitter/slow receiver use cases
- To switch from a dynamic push-pull strategy to a push-based only strategy if you request
At first glance, the
Publisher interface seems deceivingly simple to implement; but doing so in complete conformance with the specification turns out to be pretty hard, and users can’t do anything with raw
Publisher except subscribing to it! That’s why it’s typically a better idea to rely on a Reactive Streams implementation, such as Reactor, to help you out with this.
Note that Java 9 will include the Reactive Streams interfaces in the
java.util.concurrent.Flow container class, further showing the relevance of Reactive Streams within the JDK.
It is also important to notice that convergence toward Reactive Streams and Reactor conversion capabilities allow easy and efficient conversion from one Reactive type to another at runtime.
I hope this blog post will help you to have a better understanding of Reactive types.
We are working on Reactive support with types like Reactor
Flux in various Spring projects like Spring Framework, Spring Boot, Spring Data, Spring Security and Spring Cloud.
But your upcoming Reactive application will also use directly these types too, for example at
@Controller methods level, because building a Reactive application means using Reactive semantics where you have to deal with latency or streams (we will also provide some guidance to integrate blocking API).
We will post additional Reactive blog posts in the upcoming months. Feel free to familiarize yourself with this test-driven Lite Rx API Hands-On that will teach you how to use
Mono, and as usual your feedbacks are welcome!
If you happen to be in Barcelona mid May (never a bad time to be in Barcelona anyway!), don’t miss the chance to join the Spring I/O conference. Also, the registration for SpringOne Platform (early August, Las Vegas) has opened recently, in case you want to benefit from early bird ticket pricing.