Modern applications are required to do many tasks at the same time, in the optimal way, and be very quick to respond to consumers’ input and expectations.
Moreover, software needs to adapt to changing scenarios, in a consistent and resilient fashion.
Thanks to Charles Darwin, we all know that the ability to adapt is the key to evolution for the species of the animal realm. But what about software development?
What is the key to survive in the code jungle?
While there is not a single answer to this big question, you may find some inspiration at Codemotion Milan 2019, attending to Grace Jansen’s talk Reacting to the future of application architecture on what we can learn from successful biological models that are naturally occurring in our world (more info in the agenda). Last tickets are still available: get yours here!
In the meantime, we could ask ourselves: why are reactive systems so fashionable these days? The reactive paradigm looks like the key to manage the complexity of modern software architecture, so it may be worth to have at least an overall understanding of its basic features.
Reactive systems for the masses
The reactive manifesto describes what a reactive architecture looks like: responsive, resilient, elastic and message-driven. According to these principles, a reactive application should be conceived to work to its best, even under adverse conditions.
The manifesto really sounds like “counteracting to Murphy’s law” was a functional requirement and the main cross-cutting concern of your application.
Kidding aside, there are indeed a lot of practical hints on how to pack all these good intentions into your design and the good news is that none of that is really brand-new. Being reactive, actually, is a matter of mindset, rather than technology.
We start analyzing these principles from the last one, because the message-driven concept is, under many aspects, the cornerstone of the whole reactive way of thinking.
A message-driven system is by definition, made up of parts or components that are exchanging information to carry out their work. In order to ensure responsiveness, components must rely on asynchronous message delivery.
With reference to the way messages propagates through the system, you can think of reactive design as a good match for microservices architectures. Indeed, microservices provide a model for modular applications conceived as a set of distinct services inter-operating by means of lightweight communication protocols.
Most of the times, these protocols are based on RESTful APIs since they are stateless (i.e. lightweight on server side) and travel over HTTP requests that are by nature, asynchronous.
However, microservices architectures are not reactive by default, since there is no resilient management of messages. In this aspect, a reactive system typically adds to the microservices paradigm persistent or durable message queues to implement fail recovery strategies.
Reactive systems embrace failure as a fact, rather than a possibility. Hence the message system is also responsible to propagate errors in order to enable counteracting policies based on the feedback sent by each component.
In reactive jargon the so called back-pressure, is applied to manage varying workloads in a graceful fashion. That practically means that reactive systems need to be elastic, hence scalable and distributed across a net of homogeneous nodes with no bottlenecks or central point of failures.
In the latest years, containers have proven to be a solid and cost-effective technology to deploy services and manage the effects of their eventual failures in an automated fashion.
Therefore, many times, you will find that reactive systems are built on top of container cloud infrastructures.
So far, nothing is new and you can safely say that at architectural level, reactive design combines already existing technologies to fulfill its promises. You could indeed ask yourself which came first, reactive principles or reactive-enabling technologies?
Like the chicken and the egg, there is no definitive answer, but we can say for sure that all these abstract concepts must go down to actual code at some point, and that’s where the term reactive reaches the highest value in innovation.
The devil is in the (implementation) details
The reactive manifesto qualifies the behavior that a system should exhibit to be reactive, but there is no specific description of how it is implemented.
As an example, reactive systems can be built on top of event-driven applications, using the well known Observer design pattern. Such implementations are commonly found in many frameworks based on Object Oriented Programming languages, and they could theoretically fit in a reactive scenario too.
However, in the recent years we assisted to the rise of reactive programming as the new frontier in the implementation of distributed systems.
Reactive programming provides an alternative data model foundation based on an abstraction called stream. In other words, a stream is to reactive programming what an object is to OOP.
Streams are defined as sequences of values that evolve through time. Instead of interfacing to them with getter and setter methods in an imperative fashion like we are used to with objects in OOP, streams actively notify changes by means of subscriptions.
With reference to the classic producer/consumer scheme, when streams play the role of the producer they push the information to their consumers as soon as it is available, instead of waiting for the consumer to pull an update request.
Consumers can be in turn, other streams that transform the information received in some way and forward the results to other destinations.
But subscription in reactive programming is not just a method to notify state changes. It is indeed a way to build effective data binding between different entities addressing two of the major concerns of DDD (domain driven design) like isolation and encapsulation of data at the same time.
Instead of relying on state propagation through the copy of updated values after pull requests, subscription is implemented with the execution of asynchronous callbacks every time a new value is pushed by a stream to its subscribers.
Callbacks execution, therefore, prevents the need to store a copy of data values just for the sake of encapsulation, making state propagation automatic and safe as long as they do not have side effects on the data they receive.
In this sense, reactive state management is easily implemented according to the principles of referential transparency of functional programming. That practically means that callbacks are guaranteed to never change the values back in the source stream, thus ensuring isolation between subscribers and providing an improvement in the resiliency of the overall system without additional effort.
Beside that, streams are also called observables since the subscription mechanism provides an effective way to link state changes to actions. Observability in turn, implies the ability not only to monitor each part of the system but also to understand the reasons of eventual failures just looking at its history. Aside resiliency, this also affects the elasticity of the system, opening the way to the implementation of adaptive workload policies.
Streams provide a very high level of abstraction that makes them suitable to model every data source into a reactive system: variables, user input, data caches, sensor events or messages that travel between components of the reactive system itself.
Hence reactive programming is the founding paradigm of many frameworks that are conceived to support the development of reactive architectures with ad hoc features.
Conclusions
Modelling our application on the base of streams of information is not an intuitive and easy task at first. Nevertheless, our everyday user experience can give us a solid understanding of the abstract principles of the reactive manifesto.
Whenever we use an application that runs smoothly and consistently, rarely letting us down with some cryptic or vague error message, we know that we are probably dealing with a system designed to be reactive.
Responsiveness, resiliency and elasticity are desirable features of every software application, hence in a certain way, we can tell that reactive architectures are a natural consequence of the goals that we set for our designs.
We know from biology that the term reactive qualifies a behavior.
In software development it translates into a set of emergent properties, rather than a specific technology. Nevertheless, some frameworks are based on abstractions that are more suitable than others to achieve reactive features, hence the level of reactiveness actually depends on our attitude towards the integration of these new tools.
Don’t miss the opportunity to learn more on how to integrate reactive design into your natural way of thinking attending to Grace Jansen’s talk Reacting to the future of application architecture at Codemotion Milan 2019. Tickets are still available, get yours here!