Understanding Reactive Programming and Reactive Streams
Last modified: 17 Aug, 2020What is Reactive
Reactive in the context of software development and design, they generally mean one of below things:
- Reactive Systems (architecture and design)
- Reactive Programming (declarative event-based)
- Functional Reactive Programming (FRP)
- Reactive Streams (Standard)
Reactive System
The Reactive Manifesto is a document that defines the essential characteristics of the reactive systems to make them flexible, loosely-coupled and scalable.
- Responsive: A reactive system should provide a rapid and consistent response time and hence a consistent quality of service
- Resilient: A reactive system should remain responsive in case of random failures through replication and isolation
- Elastic: Such a system should remain responsive under unpredictable workloads through cost-effective scalability
- Message-Driven: It should rely on asynchronous message passing between system components
Reactive Systems is generally message-driven.
Reactive Programming
Reactive programming is all about handling asynchronous streams of data. It is concerned with data streams and the propagation of change.
The core of reactive programming is a data stream that we can observe and react to, even apply back pressure as well. This leads to non-blocking execution and hence to better scalability with fewer threads of execution.
In reactive programming, Observables
emit data, and send it to the subscribers
. This can be seen as data being PUSHed
in reactive programming, as opposed to data being PULLed
in imperative programming, where you explicitly request data (iterating over collection, requesting data from the DB, etc).
By being built around the core pillars of being fully asynchronous and non-blocking, Reactive Programming is an alternative to the more limited ways of doing asynchronous code in the JDK: namely Callback
based APIs and Future
.
Reactive programming is generally event-driven.
Functional Reactive Programming
Reactive programming, with the concept of Functional programming is termed as functional reactive programming a.k.a FRP
.
FRP helps us to think about asynchronous programs (high-level abstractions), makes the flow of your application easier, and improves standard error handling (data structure = less code, less bugs). That is the reactive
part. The functional
part is the reactive extensions. Rx allow you to manipulate and combine streams of events. Together, that is really the power of functional reactive programming: the ability to combine functions, operate, and transform the stream of events.
Reactive Streams
Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.
The scope of Reactive Streams is to find a minimal set of interfaces, methods and protocols that will describe the necessary operations and entities to achieve the goal—asynchronous streams of data with non-blocking back pressure.
Semantics of the implementation is defined and maintained at reactive-streams.org.
Difference between Reactive Programming and Reactive Systems
Using reactive programming does not build a reactive system.
Reactive programming is a great technique for managing internal logic and dataflow transformation, locally within the components, as a way of optimizing code clarity, performance and resource efficiency.
Reactive systems, being a set of architectural principles, puts the emphasis on distributed communication and gives us tools to tackle resilience and elasticity in distributed systems.
Reactive programming is event-driven, in contrast to reactive systems, which are message-driven.
message-driven system have long-lived addressable components where messages are inherently directed and have a clear (single) destination.
event-driven have dataflow-driven model where events are facts for others to observe.
To summarize, an event-driven system focuses on addressable event sources while a message-driven system concentrates on addressable recipients.
Reactive programming is an important step towards implementing a reactive system, but it is not the only way, But a Reactive System which uses reactive programming, both events and messages will be present—as one is a great tool for communication (messages), and another is a great way of representing facts (events).
Difference between Reactive Programming and Reactive Streams
Reactive programming is a programming paradigm where as Reactive Streams is a specification where as Reactive Streams gives us a common API for Reactive Programming in Java.
Reactive Streams gives us an API interface we can code to without needing to worry about the underlying implementation.
Building blocks of Reactive Programming
Observables
- Observables are the data source/stream that can emit multiple values, just one, or none. They can also emit errors and can be infinite or finite, in which case they emit their completion event.Subscribers
- Subscribers subscribe to Observables. They consume/observe the data and also receive the errors and completion events from the Observable.Operators
- They are used tocreate
,transform
,filter
orcombine
Observables.- create - timers, ranges, from other data sources
- transform - map, buffer, group, scan, etc
- filter - filter, distinct, skip, debounce, etc
- zip, merge, combine latest, etc
Schedulers
- It is a mechanism that allows us to easily add threading to our Observables and Subscribers.
Building blocks of Reactive Streams
The Reactive Streams API consists of just four interfaces.
Publisher
- A publisher is a provider of a potentially unbounded number of sequenced elements, publishing them according to the demand received from itsSubscriber
(s).
A Publisher
can serve multiple Subscribers
subscribed subscribe(Subscriber)
dynamically at various points in time.
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
Subscriber
- Will receive calls to Subscriber.onSubscribe(Subscription) once after passing an instance of Subscriber toPublisher.subscribe(Subscriber)
.
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
Subscription
- A Subscription represents a one-to-one lifecycle of aSubscriber
subscribing to aPublisher
. It can only be used once by a singleSubscriber
.
It is used to both signal desire for data and cancel demand (and allow resource cleanup).
public interface Subscription {
public void request(long n);
public void cancel();
}
Processor
- A Processor represents a processing stage — which is both aSubscriber
and aPublisher
and obeys the contracts of both.
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Reactive Streams Implementations for Java
There are multiple implementations available to implement Reactive streams Flow, but most of them align to the core principals as below
Java 1.8
Reactive Streams specification provides support for Java 1.8 by adding additional dependency to the project.
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>1.0.0</version>
</dependency>>
Java 9
Reactive Streams specification has been part of Java 9 API.
Reactive Streams interfaces move under the Flow class in Java 9. But other than that, the API is the same as Reactive Streams 1.0 in Java 1.8.
RxJava
RxJava is a Reactive Extensions Java implementation library for composing asynchronous and event-based programs by using observable sequences.
It extends the observer pattern to support sequences of data/events and adds operators that allow you to compose sequences together declaratively while abstracting away concerns about things like low-level threading, synchronization, thread-safety and concurrent data structures.
More information at RxJava
Reactor
Reactor is a fully non-blocking reactive programming framework for java that integrates with java 8 functional APIs.
Spring framework version 5 introduced a reactive stack called Spring webflux which is built upon reactor and is a fully reactive non-blocking servlet stack.
Reactor exposes two main operators:
- Flux — Object that publishes asynchronous sequence of 0 to N items. Consider an array, a flux can publish all the items of the array to a subscriber asynchronously.
- Mono — Object that publishes at most a single item. Mono can be empty.
More information at projectreactor.io
Ratpack
Ratpack is a set of Java libraries for building modern high-performance HTTP applications.
Ratpack uses Java 8, Netty, and Reactive principles. Ratpack provides a basic implementation of the Reactive Stream API but is not designed to be a fully-featured reactive toolkit.
More information at ratpack.io
Vert.x
Vert.x is an Eclipse Foundation project. It is a polyglot event-driven application framework for the JVM.
Reactive support in Vert.x is similar to Ratpack. Vert.x allows you to use RxJava or their native implementation of the Reactive Streams API.
More information at vertx.io
Akka Streams
Akka Streams also fully implements the Reactive Streams specification.
Akka uses Actors to deal with streaming data. While Akka Streams is compliant with the Reactive Streams API specification, the Akka Streams API is completely decoupled from the Reactive Streams interfaces.
More information at akka.io
SmallRye Mutiny
SmallRye Mutiny is a reactive programming library. It’s an event-driven reactive programming library, supporting (Reactive Streams) based back-pressure.
It reuses ideas from Reactive eXtensions but does not follow the same API guidelines and operators. Also, it can be used to build Reactive Systems, for example, by combining, Mutiny, and Reactive Messaging.
More information at smallrye.io
Glossary
Backpressure
When one component is struggling to keep-up, the system as a whole needs to respond in a sensible way. It is unacceptable for the component under stress to fail catastrophically or to drop messages in an uncontrolled fashion. Since it can’t cope and it can’t fail it should communicate the fact that it is under stress to upstream components and so get them to reduce the load.
This back-pressure is an important feedback mechanism that allows systems to gracefully respond to load rather than collapse under it.
The back-pressure may cascade all the way up to the user, at which point responsiveness may degrade, but this mechanism will ensure that the system is resilient under load, and will provide information that may allow the system itself to apply other resources to help distribute the load.
To be more specific (Java – in process), exchanging data from one Thread to another needs to be a cooperative mechanism where the consuming component needs to indicate how much data it wants and the producing component needs to reciprocate up to that amount, so as not to overload the consumer. Strategies can then be designed to indicate how producers notify interested parties when the system is under strain and cannot handle any more data or possibly scale the system to meet said demands.
Non-Blocking
In concurrent programming an algorithm is considered non-blocking if threads competing for a resource do not have their execution indefinitely postponed by mutual exclusion protecting that resource.
In practice this usually manifests as an API that allows access to the resource if it is available otherwise it immediately returns informing the caller that the resource is not currently available or that the operation has been initiated and not yet completed.
A non-blocking API to a resource allows the caller the option to do other work rather than be blocked waiting on the resource to become available. This may be complemented by allowing the client of the resource to register for getting notified when the resource is available or the operation has completed.
Flux
A Flux<T>
is a Reactive Streams Publisher, augmented with a lot of operators that can be used to generate, transform, orchestrate Flux sequences.
It can emit 0 to n <T>
elements (onNext
event) then either completes or errors (onComplete
and onError
terminal events). If no terminal event is triggered, the Flux
is infinite.
- Static factories on Flux allow to create sources, or generate them from several callbacks types.
- Instance methods, the operators, let you build an asynchronous processing pipeline that will produce an asynchronous sequence.
- Each
Flux#subscribe()
or multicasting operation such asFlux#publish
andFlux#publishNext
will materialize a dedicated instance of the pipeline and trigger the data flow inside it.
// Create a Flux that completes without emitting any item.
static <T> Flux<T> empty()
// Create a new Flux that emits the specified item(s) and then complete.
static <T> Flux<T> just(T... data)
// Create a Flux that emits the items contained in the provided Iterable.
static <T> Flux<T> fromIterable(Iterable<? extends T> it)
// Create a Flux that completes with the specified error.
static <T> Flux<T> error(Throwable error)
// Create a new Flux that emits an ever incrementing long starting with 0 every period on the global timer.
static Flux<Long> interval(Duration period)
More information at Javadoc
Mono
A Mono<T>
is a Reactive Streams Publisher
, also augmented with a lot of operators that can be used to generate, transform, orchestrate Mono sequences.
It is a specialization of Flux
that can emit at most 1 element: a Mono is either valued (complete with element), empty (complete without element) or failed (error).
A Mono<Void>
can be used in cases where only the completion signal is interesting (the Reactive Streams equivalent of a Runnable
task completing).
Like for Flux
, the operators can be used to define an asynchronous pipeline which will be materialized anew for each Subscription
.
Note that some API that change the sequence’s cardinality will return a Flux
(and vice-versa, APIs that reduce the cardinality to 1 in a Flux
return a Mono
).
Mono.just(1)
.map(integer -> "foo" + integer)
.or(Mono.delay(Duration.ofMillis(100)))
.subscribe(System.out::println);
More information at Javadoc