The term “reactive” has become a fairly loaded and sometimes confusing term, which is used to refer to many different things. You might have heard of Reactive Programming, Reactive Extensions, Reactive Streams, Reactive Messaging or Reactive Systems. But what do these terms really mean and how do they all fit together?
In this article we will attempt to define some of these key terms and where they fit together, however it is worth noting that this is an area that is constantly developing and can be highly opinionated due to many of these terms relating to concepts or specifications as opposed to implementations.
In technical terms, reactive programming is a paradigm in which declarative code is issued to construct asynchronous processing pipelines. In other words, it’s programming with asynchronous data streams that sends data to a consumer as it becomes available, which enables developers to write code that can react to these state changes quickly and asynchronously.
A stream is a sequence of ongoing events (state changes) ordered in time. Streams can emit three different things: a value (of some type), an error, or a “completed” signal. The events are captured asynchronously, by defining a function that will execute when a value is emitted, another function when an error is emitted, and another function when ‘completed’ is emitted. “Listening” to the stream is called subscribing. The functions we are defining are observers. The stream is the subject (or “observable”) being observed.
Using reactive programming, you can create data streams for anything, including: variables, user inputs, properties, caches, data structures, and so on. These streams can then be observed and actions can be taken accordingly. Reactive programming also provides a fantastic toolbox of functions to enable the combination, creation, and filtering of any of these streams, such as:
- A stream or multiple streams can be used as an input to another stream.
- You can merge two streams.
- You can filter a stream to get another one that has only those events you are interested in.
- You can map data values from one stream to another new one.
There are many patterns and tools that can be used to enable reactive programming within microservices. They include:
- Futures, which is a promise to hold the result of some operation once that operation completes.
- Observables, which is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any events (state changes), usually by calling one of their methods.
- Publish and Subscribe
- Reactive Streams, which we describe in more detail later in this article, is a programming concept for handling asynchronous data streams in a non-blocking manner while providing backpressure to stream publishers
- Reactive Programming Libraries, which is used for composing asynchronous and event-based programs (such as RxJava and SmallRye Mutiny)
Reactive programming can be a useful implementation technique for managing internal logic and data flow transformation locally within components like microservices (inter components), through this asynchronous and non-blocking execution.
Reactive programming deals with data flow and automatically propagates changes via the data flow. This paradigm is implemented by Reactive Extensions.
Reactive extensions enables imperative programming languages to compose asynchronous and event-based programs by using observable sequences. In other words, it enables your code to create and subscribe to data streams named observables. Reactive extensions combine the observer and iterator patterns and functional idioms to give you a sort of toolbox, enabling your application to create, combine, merge, filter, and transform data streams.
There are a few popular reactive extensions for Java, such as ReactiveX (which includes RxJava, RxKotlin, Rx.NET, and so on) and BaconJS. With a variety of libraries to choose from and a lack of interoperability among them, it can be hard to choose which one to use. To solve this issue, the Reactive Streams initiative was established.
Reactive Streams is an initiative that was created to provide a standard to unify reactive extensions and deal with asynchronous stream processing with non-blocking backpressure, which encompasses efforts aimed at runtime environments as well as network protocols. The
org.reactivestreams APIs, first created in 2015, contain 4 interfaces:
Processor. The extension frameworks such as RxJava, Reactor, Akka Streams all implement these interfaces.
Java developers wanted to standardize the reactive streams APIs in the JDK, so that the APIs could be freely available without having to package any third party libraries. To meet these requests, JDK9 made the reactive streams interfaces available under
java.util.concurrent.Flow, which is semantically equivalent to
org.reactivestreams APIs. RxJava, Reactor, and Akka Streams all implement the interfaces under Flow.
The Reactive Streams interfaces are:
Subscriber and Publisher. The Subscriber is a stream Observer. A Subscriber subscribes to a Publisher via the method
Publisher.subscribe(). Then the Publisher calls
Subscriber.onSubscribeto pass over the Subscription, so that the Subscriber calls
subscription.request(), which takes care of back pressure or
Subscription. If the subscriber is only capable of handling 4 items, it will pass its capacity via
Subscription.request(4). The publisher will not send more than 4 unless the subscriber requests more. The Publisher invokes
onNext()when an item is published or
onComplete()if no item is to be published.
Processor. A processor is an intermediary between Publisher and Subscriber. It subscribes to a Publisher and then a Subscriber subscribes to Processor.
As shown above, Reactive Streams introduces the concepts publish, subscribe, and a way to plumb them together. However, the streams typically need to be manipulated by maps, filters, flatMaps, and more (similar to
java.util.stream, which is available for non-reactive streams). Users are not meant to implement reactive streams APIs directly, as it is complicated and it is difficult to get it right and pass TCKs (Technology Compatibility Kit, a suite of tests, tools, and documentation that allows an implementor of a Java technology specification to determine if the implementation is compliant with the specification) for Reactive Streams. As a consequence, the implementations have to be provided by third party libraries such as Akka Streams, RxJava, or Reactor.
However, many MicroProfile enterprise application developers didn’t want to or couldn’t use third party dependencies but wanted (needed) to be able to manipulate reactive streams. So, in order to standardize the stream manipulation, MicroProfile Reactive Streams Operators was created to offer the equivalent functionality as
java.util.stream. An example of the usage of Reactive Streams Operators is shown below.
The Reactive Streams specification and the MicroProfile Reactive Streams Operators specification provides the basis for the MicroProfile Reactive Messaging specification.
As described above, Reactive Streams is a specification for doing asynchronous stream processing with back pressure. It defines a minimal set of interfaces to allow components that do this sort of stream processing to be connected together. The MicroProfile Reactive Streams Operators is an Eclipse MicroProfile specification which builds on Reactive Streams to provide a set of basic operators to link different reactive components together and to perform processing on the data which passes between them.
The MicroProfile Reactive Messaging specification allows asynchronous communication to occur between application components, enabling temporal decoupling of microservices. This temporal decoupling is necessary if communication is to be enabled to occur regardless of when the components involved in the communication are running, whether they are loaded or overloaded, and whether they are successfully processing messages or failing. It enables greater resiliency between microservices, which is a key characteristic of reactive systems.
MicroProfile Reactive Messaging was designed to provide a lighter-weight, reactive solution to messaging to ensure microservices that are written using MicroProfile are able to meet the demands required by a reactive architecture thereby providing a way to connect event-driven microservices together.
It uses annotated methods (
@Outgoing) on an application’s beans and connects them together by named channels (a string/name indicating which source or destination of messages is to be used).
Reactive programming, reactive streams, and reactive messaging are all useful tools to design and build reactive systems.
The term Reactive Systems was coined to describe an architecture style to deliver responsive and reactive applications, at the system level. It is designed to enable applications composed of multiple microservices working together as a single unit to better react to their surroundings and one another, manifesting in greater elasticity when dealing with ever-changing workload demands and resiliency when components fail. It is based upon the Reactive Manifesto outlined in the following diagram.
The Reactive Manifesto lays out four key high-level characteristics of reactive systems:
- Responsive: a reactive system needs to handle requests in a reasonable time
- Resilient: a reactive system must stay responsive in the face of failures (errors, crashes, timeouts, etc), so it must be designed to be able to gracefully handle failures
- Elastic: a reactive system must stay responsive under various loads – being able to scale both up and down.
- Message driven: components from a reactive system interact using asynchronous message passing to enable loose coupling, isolation and location transparency.
Reactive systems are asynchronous-message-driven systems at their core. Despite the seeming simplicity of the fundamental principles of reactive systems, building one of them can be tricky. Typically, each node needs to embrace an asynchronous non-blocking development model, a task-based concurrency model, and use non-blocking I/O. So, it’s important to really consider these points when designing and building your reactive system. However, using Reactive Programming and Reactive Extensions helps to provide a development model to tackle these asynchronous challenges. They can help to ensure your code remains readable, and understandable.
Implementing all of this can seem like a fairly daunting task! But, to make this a little easier, there are several open-source reactive frameworks or toolkits available to help, including Vert.x, Akka, and Project Reactor, just to name a few. These frameworks or toolkits provide API implementations that add value on top of other reactive tools and patterns (including the Reactive Streams specification, RxJava, and so on).
Summary and next steps
In this article, we attempted to define and differentiate the key terms “reactive” can refer to, and to explain how these specifications can be used together to enable the design and construction of non-blocking and reactive systems.
If you’re interested in learning more about what reactive systems are then you can download our free e-book “Reactive Systems Explained”, or you can check out our “Getting started with Reactive Systems” article. Or, if you’d like to go straight into getting hands on with reactive APIs, then check out our interactive labs here.
Gain more insights from developer advocates discussing reactive programming and reactive systems in this video.