11.1.1 Defining Reactive Streams
Reactive Streams is an initiative started in late 2013 by engineers from Netflix, Lightbend, and Pivotal (the company behind Spring). Reactive Streams aims to provide a standard for asynchronous stream processing with nonblocking backpressure.
We’ve already touched on the asynchronous trait of reactive programming; it’s what enables us to perform tasks in parallel to achieve greater scalability. Backpressure is a means by which consumers of data can avoid being overwhelmed by an overly fast data source, by establishing limits on how much they’re willing to handle.。
Java Streams vs Reactive Streams
There’s a lot of similarity between Java streams and Reactive Streams. To start with, they both have the word streams in their names. They also both provide a functional API for working with data. In fact, as you’ll see later when we look at Reactor, they even share many of the same operations.
Java streams, however, are typically synchronous and work with a finite set of data. They’re essentially a means of iterating over a collection with functions.
Reactive Streams support asynchronous processing of datasets of any size, including infinite datasets. They process data in real time, as it becomes available, with backpressure to avoid overwhelming their consumers.
On the other hand, JDK 9’s Flow APIs correspond to Reactive Streams. The
Flow.Publisher,Flow.Subscriber,Flow.Subscription, andFlow.Processortypes in JDK 9 map directly toPublisher,Subscriber,Subscription, andProcessorin Reactive Streams. That said, JDK 9’s Flow APIs are not an actual implementation of Reactive Streams.
The Reactive Streams specification can be summed up by four interface definitions: Publisher, Subscriber, Subscription, and Processor. A Publisher produces data that it sends to a Subscriber per a Subscription. The Publisher interface declares a single method, subscribe(), through which a Subscriber can subscribe to the Publisher, as shown here:
public interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}Once a Subscriber has subscribed, it can receive events from the Publisher. Those events are sent via methods on the Subscriber interface as follows:
public interface Subscriber<T> {
void onSubscribe(Subscription sub);
void onNext(T item);
void onError(Throwable ex);
void onComplete();
}The first event that the Subscriber will receive is through a call to onSubscribe(). When the Publisher calls onSubscribe(), it passes a Subscription object to the Subscriber. It’s through the Subscription that the Subscriber can manage its subscription, as shown next:
public interface Subscription {
void request(long n);
void cancel();
}The Subscriber can call request() to request that data be sent, or it can call cancel() to indicate that it’s no longer interested in receiving data and is canceling the subscription. When calling request(), the Subscriber passes in a long value to indicate how many data items it’s willing to accept. This is where backpressure comes in, preventing the Publisher from sending more data than the Subscriber is able to handle. After the Publisher has sent as many items as were requested, the Subscriber can call request() again to request more.
Once the Subscriber has requested data, the data starts flowing through the stream. For every item that’s published by the Publisher, the onNext() method will be called to deliver the data to the Subscriber. If there are any errors, onError() is called. If the Publisher has no more data to send and isn’t going to produce any more data, it will call onComplete() to tell the Subscriber that it’s out of business.
As for the Processor interface, it’s a combination of Subscriber and Publisher, as shown here:
public interface Processor<T, R>
extends Subscriber<T>, Publisher<R> {}As a Subscriber, a Processor will receive data and process it in some way. Then it will switch hats and act as a Publisher to publish the results to its Subscribers.
As you can see, the Reactive Streams specification is rather straightforward. It’s fairly easy to see how you could build up a data processing pipeline that starts with a Publisher, pumps data through zero or more Processors, and then drops the final results off to a Subscriber.
What the Reactive Streams interfaces don’t lend themselves to, however, is composing such a stream in a functional way. Project Reactor is an implementation of the Reactive Streams specification that provides a functional API for composing Reactive Streams. As you’ll see in the following chapters, Reactor is the foundation for Spring’s reactive programming model. In the remainder of this chapter, we’re going to explore (and, dare I say, have a lot of fun with) Project Reactor.
