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.
Then, we'll then dive deeper into reactive systems (an architectural style that enables applications composed of multiple microservices to work together as a single unit in order to better react to their surroundings and one another). Reactive systems are already in use in a wide variety of industries and use cases. But, how is this defined, and what makes a reactive system truly reactive? And, importantly, how can we implement this in our own applications?
Defining the term "reactive"
Let's break down all these terms: reactive programming, reactive extensions, reactive streams, reactive messaging and reactive systems.
Reactive programming
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 extension
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
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: Publisher, Subscriber, Subscription, and 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.onSubscribe to pass over the Subscription, so that the Subscriber calls subscription.request(), which takes care of back pressure or subscription.cancel().
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.
Reactive messaging
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 (@Incoming and @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 systems
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).
Build Reactive microservices today with Open Liberty
There are many different ways to implement a reactive system. Each framework achieves concurrency, parallelism, resiliency, and messaging in a variety of ways, and interacts with the underlying infrastructure differently. So, it's important to fully understand the capabilities of the implementation you choose.
Reactive Messaging for MicroProfile is an open-source specification that provides asynchronous messaging support based on MicroProfile Reactive Streams. MicroProfile Reactive Messaging provides a very easy-to-use way to send, receive, and process messages, and is well-suited to writing applications that process streams of events. Reactive Messaging uses a model of annotated methods that are connected by named channels. In the blog post "Sending and receiving messages between microservices with MicroProfile Reactive Messaging" (Open Liberty), Andrew Rouse and Gordon Hutchison describe how to send and receive messages between microservices using MicroProfile Reactive Messaging. MicroProfile Reactive messaging, although not an end-to-end reactive solution, enables non-blocking, asynchronous message passing between services, giving them the ability to scale, fail, and evolve independently, and to remain unaffected by each other's availability. MicroProfile Reactive Messaging can be used alongside Open Liberty to create reactive systems.
Try it for yourself and see how you could implement this in your own Java applications, in the Open Liberty reactive service guides.
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" which provides a succinct and clear summary of reactive systems: what they are; when it's best to use them; key patterns used when designing and creating them; and what frameworks and toolkits are available to help build them. Or, for a deeper, structured learning path, learning about Reactive Systems from design all the way through to production, then take a look at the "Lightbend Reactive Architecture" course.
About cookies on this siteOur websites require some cookies to function properly (required). In addition, other cookies may be used with your consent to analyze site usage, improve the user experience and for advertising.For more information, please review your cookie preferences options. By visiting our website, you agree to our processing of information as described in IBM’sprivacy statement. To provide a smooth navigation, your cookie preferences will be shared across the IBM web domains listed here.