In the last several years, the term Reactive has received much buzz in IT circles but has also become ambiguous to the point that I feel some clarification is needed. Do you know what someone means when using the term “reactive” beyond general software design goodness? When YoppWorks uses the term, we are generally referring to “Reactive System” or “Reactive Architecture” which we defined below. Subsequently, other uses of the term “Reactive” are contrasted to how YoppWorks uses the term.
The original Reactive Manifesto (v1.0) circa 2013 defined Reactive from the Merriam-Webster dictionary as “readily responsive to a stimulus” and noted that this definition captures the essence of reactive systems. It went on to indicate what a reactive system reacts to:
- Events – The event-driven nature enables the following qualities
- Load – Focus on scalability rather than single-user performance
- Failure – Build resilient systems with the ability to recover at all levels
- Users – Combine the above traits for an interactive user experience
That definition was revised in the Reactive Manifesto 2.0 in 2014 and garnered a little over 30,000 signatories (as of September 2021). This update made the meaning more clear by indicating that reactive systems are:
- Responsive – The system responds promptly if at all possible.
- Resilient – The system stays responsive in the face of failure.
- Elastic – The system remains responsive under varying workloads.
- Message-driven – Reactive Systems rely on asynchronous message-passing to establish a boundary between components that ensures loose coupling, isolation, and location transparency
- Stay Responsive – Always respond in a timely matter
- Accept Uncertainty – Build reliability despite unreliable foundations
- Embrace Failure – Expect things to go wrong and design for resilience
- Assert Autonomy – Design components that act independently and interact collaboratively
- Tailor Consistency – Individualize consistency per component to balance availability and performance
- Decouple Time – Process asynchronously to avoid coordination and waiting
- Decouple Space – Create flexibility by embracing the network
- Handle Dynamics – Continuously adapt to varying demand and resources
- Partition State – Divide state into smaller chunks to leverage parallelism of the system
- Communicate Facts – Choose immutable event streams over mutable state
- Isolate Mutations – Contain and isolate mutable state using bulkheads
- Coordinate Dataflow – Orchestrate a continuous steady flow of information
- Localize State – Take ownership of data by co-locating state and processing
- Observe Communications – Understand your system by looking at its dynamics
The evolution of the definition of “Reactive” from the “Reactive Manifesto,” as discussed previously, focuses on large distributed systems, compared to the other definitions. That is, the principles espoused aim at the construction of a software system for a large enterprise that must always work, scale elastically, and be responsive to millions of users.
Correspondingly, the manifesto’s and foundation’s perspective on reactive tend towards solving some of the most challenging problems in software systems: distributed computing, fault tolerance, load balancing, availability vs. consistency (CAP Theorem), sharding & partitioning, elastic scalability, resource contention, real-time streaming, all while maintaining responsiveness despite all of these concerns.
So, in essence, Reactive in this context is about the construction of Reactive Systems. This Kevin Weber article from RedElastic on Medium, written about the time of the Reactive Manifesto, and despite being billed as an explanation of Reactive Programming, is an excellent summary of the internet-scale trend that prompted the need for Reactive Systems and the basic principles of the Reactive Manifesto.
We build Reactive Systems from a set of principles that are collectively known as Reactive Architecture. The principles involved encompass everything in the Reactive Foundation’s principles and patterns (as described above) as well as several others, such as:
- We use the Hexagonal Architecture principles to keep API, Domain Logic, and Infrastructure very separate.
- We use various techniques to support embracing failure (Let It Fail) with the following patterns: circuit breaker, timeout with retry, microservices, bulkheading, error queues, sagas, adaptors for external systems, supervision hierarchies, and similar patterns.
- We acknowledge that Amdahl’s Law and Gunther’s Universal Scalability Law help design systems that maximize concurrency by minimizing both contention (access to shared resources) and coherency (time needed to achieve consistency).
Reactive Extensions (ReactiveX, Rx...)
In 2011, two years before the Reactive Manifesto, Microsoft released Reactive Extensions for .net to help with composing asynchronous and event-based programs around an extension to the observer pattern. ReactiveX describes itself this way:
“ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences.
It extends the observer pattern to support sequences of data and/or 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, concurrent data structures, and non-blocking I/O.”
In other words, ReactiveX supports a model of programming that is message-driven (“sequences of data or events”), supports a kind of functional composition, and hides all the details of non-blocking I/O. That’s great! It focuses on the Message-driven aspect of the Reactive Manifesto. However, it only supports resiliency tangentially and does not support elastic scalability at all. Still, it does have a beneficial effect on responsiveness because it supports asynchronous, non-blocking interactions in a program.
Since ReactiveX abstracts away concerns about things like low-level threading, synchronization, thread-safety, and concurrent data structures, those things remain hidden but still handled at the application level (at its peril). The intent is to use non-blocking asynchronous events from the Observable, but nothing enforces this. Similarly, in Akka, nothing enforces immutability, not using synchronization primitives, nor using blocking operations in the handling of an actor message processing; even though none of those things should be done.
ReactiveX is for programming an event-driven system that is typically not distributed. In monoliths, both resiliency and scalability are hard to achieve since there is always a single point of failure or only one thing to scale (typically vertically). The Reactive Manifesto’s Glossary had this to say comparing event-driven systems and message-driven systems:
“Resilience is more difficult to achieve in an event-driven system due to the short-lived nature of event consumption chains: when processing is set in motion and listeners are attached in order to react to and transform the result, these listeners typically handle success or failure directly and in the sense of reporting back to the original client. Responding to the failure of a component in order to restore its proper function, on the other hand, requires a treatment of these failures that is not tied to ephemeral client requests, but that responds to the overall component health state.”
In my view, ReactiveX is like Reactive Programming because it is “reactive” in a small way by only concerning itself with a single program using the Message-driven tenet of the Reactive Manifesto. ReactiveX did not aim to support distributed systems with programs interacting via a network nor attempt to achieve resiliency or scalability.
This 2020 DZone article, Introduction to Reactive Programming, describes this term in much the same spirit as ReactiveX above. It describes the Observer/Observable pattern, its use of operators on the events, and even uses the ReactiveX RxJava library in its examples. While the introduction to that article does reference all four tenets of the Reactive Manifesto, it fails to provide examples or any explanation of Resiliency, Elasticity, or Responsiveness.
I use this article because:
- it is was written relatively recently (April 8, 2020),
- was read by over twenty thousand people,
- it shows how the use of the term Reactive can leave out most of the essential tenets of the Reactive Manifesto.
I believe that Reactive Programming means “using ReactiveX” or at least using the Observer/Observable pattern to support a composable, asynchronous, event-driven programming style, as described previously. Many articles that describe Reactive Programming reinforce this idea:
- Wikipedia: Reactive programming
- Medium: What Is Reactive Programming?
- RedHat: 5 Things To Know About Reactive Programming
- Tutorials Point: Reactive Programming
Section 5 of the RedHat article asserts that Reactive Programming != Reactive System, to which I agree. I believe Reactive Programming is a subset of a Reactive System. For me, Reactive Programming, and similarly ReactiveX, mean:
- Only ascribing to the “Message-driven” tenet of the Reactive Manifesto.
- Supporting responsiveness only tangentially as a result of asynchronous non-blocking processing.
- Not addressing Resiliency nor Scalability.
- Only concerned with programming (constructing a single program) and not distributed computing (creating a system of many cooperating programs).
That is, it is reactive, much like Reactive Programming and ReactiveX. It is message-driven and supports one aspect of resiliency by providing a mechanism for dealing with back-pressure (clogging of message streams because consumers are not consuming as fast as producers are producing).
Reactive Streams is a member project of the Reactive Foundation along with RSocket and R2DBC. RSocket provides a binary protocol for reactive streams intended for byte stream transports such as TCP, WebSocket, and Aeron. R2DBC is like JDBC, but instead of the one-thread-per-connection model in JDBC drivers, it opts for a more scalable and reactive approach using a non-blocking, asynchronous, back-pressure aware, and reactive streams-oriented interface to relational databases.
Reactive Streams are vital to Reactive Systems as they support the construction of resilient data streams in a distributed system that can prevent producers from overloading consumers. This feature helps alleviate many “buffer full” or “out of space” conditions that plagued message queue systems for decades. Additionally, Akka Streams builds on top of the Reactive Streams foundation for the JVM.
Reactive Core (Spring WebFlux)
The Spring WebFlux documentation was courteous enough to define what they mean by “Reactive” with regards to WebFlux and its Reactive Core:
“The term “reactive” refers to programming models that are built around reacting to change — network component reacting to I/O events, UI controller reacting to mouse events, etc. In that sense non-blocking is reactive because instead of being blocked we are now in the mode of reacting to notifications as operations complete or data becomes available.
There is also another important mechanism that we on the Spring team associate with “reactive” and that is non-blocking back pressure. In synchronous, imperative code, blocking calls serve as a natural form of back pressure that forces the caller to wait. In non-blocking code it becomes important to control the rate of events so that a fast producer does not overwhelm its destination.
Reactive Streams is a small spec, also adopted in Java 9, that defines the interaction between asynchronous components with back pressure. For example a data repository — acting as Publisher, can produce data that an HTTP server — acting as Subscriber, can then write to the response. The main purpose of Reactive Streams is to allow the subscriber to control how fast or how slow the publisher will produce data.”
This definition is similar to a combination of Reactive Programming + Reactive Streams but only in the context of HTTP-based services (typically REST). This combination provides the building blocks for a limited form of Reactive System. Still, it does not utilize persistent message queues (for example, Kafka or Pulsar) as an option for communication between distributed components. Consequently, without the bulkheading principle available through a persistent message queue, resiliency is not well supported. Reactive Core does not appear to address elastic scalability since there are no facilities for cooperating services to form a cluster that can grow and shrink horizontally, as may be needed in larger Reactive Systems. For those familiar with the Akka Platform, Spring WebFlux is roughly similar to using Akka Streams and Akka HTTP together in a cohesive fashion but without using Actors or any Clustering features (Sharding, CQRS, CRDTs, and event sourcing persistence).
ReactJS and MVC Applications
While Merriam-Webster defines “reactive” as “readily responsive to a stimulus” is relevant to ReactJS and ReactNative, this use of the term “reactive” has nothing to do with the tenets of the Reactive Manifesto other than “Responsive.” Furthermore, ReactJS and React Native could not form a Reactive System, as described above, independently.
Functional Reactive Programming (FRP)
Functional Reactive Programming originated in the 1990s, long before ReactiveX or the Reactive Manifesto existed. It originated from interactive 3D computer graphics and defined a general paradigm for describing time-varying information using expressions that describe entire evolutions of values over time, representing these evolutions directly as first-class values. This use of the term “reactive” is appropriate for this programming style but wholly orthogonal to the kind of “reactive” we’ve defined above. For more on this, please see Conal Elliott’s 2015 talk about the essence and origins of FRP.
A 2012 Survey on Reactive Programming paper by Bainomugisha, Carreton, Cutsem, Mostinckx, and Meuter, all from Universiteit Brussel, makes an interesting point about Reactive Programming:
“Reactive programming tackles issues posed by event-driven applications by providing abstractions to express programs as reactions to external events and having the language automatically manage the flow of time (by conceptually supporting simultaneity), and data and computation dependencies. This yields the advantage that programmers need not to worry about the order of events and computation dependencies. Hence, reactive programming languages abstract over time management, just as garbage collectors abstract over memory management. This property of automatic management of data dependencies can be observed in spreadsheet systems, arguably the most widely used end-user programming language.”
The article also indicates that ReactiveX derives from earlier work in Haskell and Scheme regarding FRP.
YoppWorks’ technology niche is with Reactive Systems and Reactive Architecture as variously defined by the Reactive Manifesto, the Reactive Foundation, and a wide range of distributed computing and programming patterns. While we definitely can do Reactive Programming and utilize frameworks like ReactiveX, WebFlux, ReactJS, and React Native, we are more often concerned with developing systems with much stronger resiliency, scalability, and elasticity requirements.
We strongly use and recommend the same Reactive Programming principles: fully asynchronous, non-blocking, back-pressure aware, streams of immutable events and other messages. However, we also make excellent use of Akka Actors and the Akka Platform’s advantages to building Reactive Systems.
The following sections provide a few reasons we’ve adopted a more detailed definition of “reactive” that aligns with Reactive Architecture.
Reactive Microservice Architecture
Many of our engagements utilize the Reactive Microservice Architecture pattern, fully described in the Reactive Microservices Architect book, by Lightbend CEO Jonas Bonér. Essentially, a Reactive Microservice Architecture can make use of these principles:
- Reactive Microservices correspond to a bounded context (from Domain Driven Design) and implement an entity (object) by processing that entity’s commands, queries, events, and results.
- Reactive Microservices isolate themselves in four different dimensions:
- Failure: failures don’t propagate to other services,
- Space: it doesn’t matter where the service runs, locally or remote,
- Time: everything is asynchronous), and
- State: uses any kind of storage that nothing else reads or writes.
- Reactive Microservices typically have GRPC or persistent message queue APIs; in either case, messages, typically in protobuffers, define both the API’s input (commands and queries) and output (events and results).
- Reactive Microservices utilize CQRS (Command Query Responsibility Segregation), which is shown in the diagram below by the separation of the Write Side (processing of commands) from the Read Side (processing of queries). This segregation helps tune database operations by utilizing different kinds of storage for events and projections of those events into a (typically) relational form. This segregation eliminates the contention of a singular relational database schema design that simultaneously optimizes performance on both the read and write sides. Martin Fowler more simply defines CQRS as “the notion that you can use a different model to update information than the model you use to read information.”
- Reactive Microservices often use event-sourcing with CQRS to record a complete history of every change event for each entity. In environments with numerous change events for an entity, a snapshot used with recovery or rehydration reduces the loading time when reading an entity into memory.
- Reactive Microservices keep a cache of the most recently used entities in memory for fast state updates and simple high-speed queries.
- Reactive Microservices define a model around the domain-driven design concept of an “aggregate root entity.” An aggregate root is merely the root entity of a tree of entities. The aggregate root is responsible for processing all the commands and queries. This approach means the API can be stable across all the aggregated entities even if the aggregation structure changes.
- The Akka Platform supports all of the techniques listed above superbly.
Implications of Reactive Architecture Principles
There are many implications of opting to use a Reactive Architecture for building a software system. The most important implications that our customers find important are:
- Compared to a complex monolithic application, the disciplined use of single-purpose, isolated microservices reduces the software engineers’ cognitive load.
- Compared to using memory caches such as Redis or Memcached, Akka Cluster Sharding significantly lowers the latency for many requests by caching actors in service memory.
- Depending on the needs of the business domain, a service may choose between availability (via Conflict-Free Replicated Datatypes) and consistency (via single writer principle and event sourcing), per the CAP Theorem.
- A service may balance Availability and Consistency concerns by using Replicated Event Sourcing. This approach departs from the efficiency and consistency of the single-writer principle for a more fault-tolerant solution with multiple writers (presumably in different availability zones or data centers), which all get the same set of events to write. This approach provides redundancy when infrastructure failures occur to improve availability at the cost of more implementation complexity and eventual consistency between the replicas.
- Significantly improved resiliency and tolerance of failures because Akka assists with various resiliency patterns: bulkheading, circuit breakers, retry, supervisors, and clustered redundancy.
- Services enhance resiliency through the elimination of single points of failure and through the encapsulation of unreliable external systems.
- Services achieve significantly better scalability by understanding Amdahl’s Law and Gunther’s Universal Scalability Law, which teaches us that adding more processing doesn’t provide linear capacity growth.
- A simpler programming model, Typed Actors, doesn’t require any synchronization points and it can change its behavior based on the events it receives. That last point makes implementing high-speed state machines relatively easy.
- Services can achieve real-time delivery of information through orchestrating a continuous and steady flow of information.
- Services can eliminate and correct state bugs through event sourcing and isolation of state.
Making the Future Easier
All of the preceding can make a system architect’s head swim. There is a lot of theory to learn and many habits to unlearn. Going from monolithic, strong consistency, synchronous system designs to distributed, eventual consistency, asynchronous system design requires a complete shift in thinking about the techniques and patterns needed to build Reactive Systems.
Fortunately, in 2022 there will be a solution that helps to reduce that complexity: Akka Serverless (short video, quick overview, documentation, source code). Lightbend, in response to the deep educational requirements its customers needed to make use of Akka, decided to simplify the construction of Reactive Systems. They also expanded on what Serverless (e.g. Amazon Lambda) means by making it stateful. Here’s what we have to look forward to:
- A drastically lower learning curve compared to Akka Platform
- Very simple programming model (parameters and state in, results and events out) that doesn’t involve actors, just a GRPC based service.
- All the features of Akka (geo-replication, scaling up and down, self-healing, distribution, actor-based, resilience) without any of the complexity because the Akka Platform powers Akka Serverless.
- Akka Serverless handles all the details about storage. Database server configurations, connection pools, schema management, and partitioning are all handled for you.
- Akka Serverless handles most infrastructure concerns by applying industry-standard deployment practices to Kubernetes. Users benefit from minimal infrastructure configurations, zero operating systems to patch, elimination of new hardware requests, and server management.
So, when you want to modernize your IT systems with Akka – either Platform or Serverless, Yoppworks has you covered. We are a global leader in this technology space.
To find out more, contact us. We would be happy to discuss your needs and situation.