Introduction
Reactive programming is a programming paradigm that encourages a data processing approach which is asynchronous, non-blocking, event-driven. Reactive programming involves modeling data and events as observable streams of data and implementing routines for data processing to react to changes in those streams.
Microsoft created the Reactive Extensions (Rx) library within the .NET ecosystem as a first step in the direction of reactive programming. RxJava then implemented reactive JVM programming. As time went on, a standardization for Java emerged through the effort of Reactive Streams, a specification defining a set of interfaces and rules of interaction for reactive libraries on the JVM. Under the Flow class its interfaces were integrated into Java 9.
The reactive programming paradigm is often presented as an extension of the observerver design pattern in object-oriented languages. You can also compare the main reactive stream pattern with the familiar Iterator design pattern, since all of these libraries contain a duality to the Iterable-Iterator pair. One major difference is that while an Iterator is pull-based, reactive streams are push-based.
Reactive programming also contributes to a significant change to composition of async logic from imperative to declarative. It's comparable to writing blocking code vs using Java 8's CompletableFuture to compose follow-up actions using lambda expressions.
Blocking
Modern applications can reach huge numbers of simultaneous users, and although modern hardware capabilities have continued to improve, software performance is still a key concern.
broadly speaking, There are two ways one could improve the performance of a program:
- Parallels the use of more threads and hardware resources.
- Attempt higher efficiency in the way current resources are being used.
Java developers typically write programs through the use of blocking code. This practice is fine until there is a bottleneck to the performance. It is then time to introduce additional threads, executing similar blocking code. But this scaling of the use of resources can quickly introduce problems of contention and competition.
Worse yet, resources are being blocked from wastes. If you look closely, as soon as a program involves some latency (I/O, such as requesting a database or calling a network), resources are wasted because threads (maybe many threads) are now sitting idle, waiting for the data.
So the approach to parallelisation is not a silver bullet. Access to the full power of the hardware is necessary but it is also complex to reason about and susceptible to waste of resources.
Asynchronicity
By writing asynchronous, non-blocking code, you allow the execution to switch to another active task which uses the same underlying resources and then returns to the current process once the asynchronous processing is complete.
Java offers two models of programming asynchronous:
Callbacks
Asynchronous methods do not have a return value, but instead take an additional callback parameter (a lambda or anonymous class) which is called when the result is available. A well-known example is the Hierarchy of Swing's EventListener.
Futures
Asynchronous methods return a Future<T>
straight away. A T value is calculated by the asynchronous process but the Future object wraps access to it. The value isn't available immediately, and the object can be polled until the value is available. An ExecutorService
that runs Callable<T>
tasks, for example, uses Future objects.
Callbacks
If we pass some function as an argument to another function and then call that argument back from the executing function, then it is a callback function.
By default a Callback function does not provide the needed asynchronous functionality, however by combining with a listener, it can be used asynchronous.
Callbacks are difficult to compose together, leading quickly to code that is hard to read and maintain (known as the "Callback Hell"). the following example show a simple Callback function where we get a number by passing a function as argument:
public class Callback {
public static void getNumber(Consumer<Double> callback) {
double number = 123.00;
System.out.println("Get number...");
// call back our callback function
callback.accept(number);
}
public static void main(String... args) {
// here we are passing a consumer function
// as an argument
getNumber((number) -> {
System.out.println("number is:" + number);
});
}
}
Futures
Future objects are a bit better than callbacks, but they still aren't doing well in composition despite the improvements that CompletableFuture brought in Java 8. It's doable but not easy to orchestrate multiple Future objects together. Future has other issues too:
- Ending another blocking situation with Future objects is easy by calling the get method.
- They don't support lazy computing.
- They lack multiple-value support and advanced error handling.
Consider another example that shows the usage of Future together with Callable.
ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// Perform some computation
Thread.sleep(1000);
return "Return some result";
}
};
System.out.println("Submitting Callable");
Future<String> future = executorService.submit(callable);
// This line executes immediately
System.out.println("Do something else while callable is getting executed");
System.out.println("Retrieve the result of the future");
// Future.get() blocks until the result is available
String result = future.get();
System.out.println(result);
From Imperative to Reactive Programming
Most of Reactive implementations aim to address the disadvantages of asynchronous "classic" approaches on the JVM while also focusing on a few additional aspects:
Composability and Readability
Reactor offers a multitude of options in composition. The ability to orchestrate tasks is closely tied to code readability and maintenance. You need to have a callback executed from a callback for complex processes, nesting itself inside another callback, etc.
The Assembly Line Analogy
Think of processing the data as moving across an assembly line. Both the conveyor belt and the workstations are reactor. If at one point there is a glitch or clogging, the affected workstation might signal upstream to limit the flow.
Operators
In our assembly analogy operators in Reactor are the workstations. Each operator adds a Publisher to the behavior and wraps the Publisher of the previous step into a new instance. Therefore the entire chain is linked, so data originates from the first Publisher. see list of ReactiveX operators.
Nothing Happens before subscribe()
In Reactor, the data do not start pumping into it by default when you write a Publisher chain. You are creating an abstract description of your synchronous process, instead. By the subscription act, the Publisher is connected to a Subscriber, which triggers the flow of data in the whole chain.
Backpressure
Backpressure is also used to relay signals upstream. Backpressure is a feedback signal sent to the line when a workstation is working more slowly than a workstation upstream. A user should operate in unbounded mode and allow all the data to be pushed by the source at its fastest reachable rate.
Hot vs Cold
Reactive libraries from the Rx family distinguish two broad categories of reactive sequences: hot and cold. For every User a Cold series** begins anew**, even at the data source. a Hot sequence does not begins from scratch. Some hot reactive streams can cache or replay an emission history.
Reactive Programming in Java
Java isn't a "reactive language" in the sense it doesn't support native coroutines. There are other languages on the JVM (Scala and Clojure) that are more natively tolerant of reactive models, but Java itself is not up until version 9. Nevertheless, Java is a powerhouse for the development of enterprises and there has been a lot of interest recently in offering Reactive layers on top of the JDK. We 're only taking a really quick look at some of these here.
Reactive Streams
Reactive Streams is a low-level contract, expressed as a handful of Java interfaces (plus a TCK). Reactive Streams have been incorporated into the JDK as java.util.concurrent.Flow in version 9. The project is a partnership between Kaazing, Netflix, Pivotal and Red Hat engineers;
RxJava
Netflix were using reactive patterns internally for some time. They released the tools they were using under an open source license as Netflix/RxJava. Netflix does a lot of programming in Groovy on top of RxJava.
Reactor
Reactor is an open source Java framework from the Pivotal. It builds directly on Reactive Streams, therefore a bridge is not required. The Reactor IO initiative, like Netty and Aeron, offers wrappers around low-level network runtimes.
WebFlux
Spring Framework 5.0 integrates reactive features including HTTP server and application building tools. Current Spring users can find a very familiar programming model in the web tier. Spring builds on Reactor but also exposes APIs that allow a choice of libraries to express its features (e.g. Reactor or RxJava)
Ratpack
Ratpack is a collection of libraries designed to build high performance services over HTTP. It builds on Netty, and integrates interoperability with Reactive Sources. Spring is supported as a native variable, and may be used to insert dependency.
Akka
Akka is a toolkit for developing applications using the Scala or Java Actor model, with interprocess communication using Akka Streams, and contracts for Reactive Streams.