One of the main principles for employing a reactive microservices-based architecture is the algorithmic design pattern divide and conquer: the decomposition of the system into discrete and isolated subsystems communicating over well-defined protocols. Isolation is a prerequisite for resilience and elasticity, and requires asynchronous communication boundaries between services to decouple them in:
- Time: allowing concurrency
- Space: allowing distribution and mobility—the ability to move services around.
Let’s dive a little deeper into two of the traits that make up a reactive microservice: isolation and autonomy.
Isolate all the things
Isolation is the most important trait. It is the foundation for many of the high-level benefits in microservices. But it is probably also the trait that has the biggest impact on your design and architecture. It will, and should, slice up the whole architecture, and therefore it needs to be considered from day one. It will even impact the way you break up and organize the teams and their responsibilities, as Melvyn Conway discovered and was later turned into Conway’s Law in 1967:
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.
Failure isolation—to contain and manage failure without having it cascade throughout the services participating in the workflow—is a pattern sometimes referred to as bulkheading. Resilience—the ability to heal from failure—is dependent on compartmentalization and containment of failure, and can only be achieved by breaking free from the strong coupling of synchronous communication. Microservices communicating over a network boundary using asynchronous message-passing enable the level of indirection and decoupling necessary to capture and manage failure, orthogonally to the regular workflow, in a supervision hierarchy.
Isolation between services makes it natural to adopt continuous delivery. This allows you to safely deploy applications and roll out and revert changes incrementally—service by service.
Isolation also makes it easier to scale each service, allowing them to be monitored, debugged, and tested independently—something that is very hard if the services are all tangled up in the big bulky mess of a monolith.
Isolation is a prerequisite for autonomy. Only when services are isolated can they be fully autonomous and make decisions independently, act independently, and cooperate and coordinate with others to solve problems.
An autonomous service can only promise its own behavior by publishing its protocol/API. Embracing this simple yet fundamental fact has a profound impact on how we understand and model collaborative systems with autonomous services.
Another aspect of autonomy is that if a service only can make promises about its own behavior, then all information needed to resolve a conflict or to repair under failure scenarios are available within the service itself, removing the need for communication and coordination.
Working with autonomous services opens up flexibility around service orchestration, workflow management, and collaborative behavior—as well as scalability, availability, and runtime management. However, this comes at the cost of putting more thought into well-defined and composable APIs, which can make communication—and consensus—a bit more challenging.
When designing individual reactive microservices, it is important to adhere to the core traits of isolation and autonomy. And remember, microservices are collaborative in nature and only make sense as systems. It is in between the microservices that the most interesting, rewarding, and challenging things take place, and learning from past failures and successes in distributed systems and collaborative services-based architectures is paramount.
For more information about Lightbend's (formerly Typesafe’s) open source microservices framework Lagom, visit the Lightbend website.
This post is a collaboration between O'Reilly and Lightbend. See our statement of editorial independence.