Chapter 1. Essential Traits of an Individual Microservice

In my previous book, Reactive Microservices Architecture, I discussed the essential traits of a microservice: isolation, autonomicity, single responsibility, exclusive state, and mobility. Let’s take a few minutes to recap the essence of these traits.

Isolate All the Things

Without great solitude, no serious work is possible.

Pablo Picasso

Isolation is the most important trait and the foundation for many of the high-level benefits in microservices.

Isolation also has the biggest impact on your design and architecture. It will, and should, slice up the entire architecture, and therefore it needs to be considered from day one.

It will even affect the way you break up and organize the teams and their responsibilities, as Melvyn Conway discovered in 1967 (later named Conway’s Law):

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.

Isolation between services makes it natural to adopt Continuous Delivery (CD). This makes it possible for you to safely deploy applications and roll out and revert changes incrementally, service by service.

Isolation makes it easier to scale each service, as well as allowing them to be monitored, debugged, and tested independently—something that is very difficult if the services are all tangled up in the big bulky mess of a monolith.

Act Autonomously

In a network of autonomous systems, an agent is only concerned with assertions about its own policy; no external agent can tell it what to do, without its consent. This is the crucial difference between autonomy and centralized management.

Mark Burgess, Promise Theory

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.

Working with autonomous services opens up flexibility around service orchestration, workflow management, and collaborative behavior, as well as scalability, availability, and runtime management, at the cost of putting more thought into well-defined and composable APIs.

But autonomy cuts deeper, affecting more than the architecture and design of the system. A design with autonomous services allows the teams that build the services to stay autonomous relative to one another—rolling out new services and new features in existing services independently, and so on.

Autonomy is the foundation on which we can scale both the system and the development organization.

Single Responsibility

This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together.

Doug McIlroy

The Unix philosophy1 and design has been highly successful and still stands strong decades after its inception. One of its core principles is that developers should write programs that have a single purpose—a small, well-defined responsibility, and compose it well so it works well with other small programs.

This idea was later brought into the Object-Oriented Programming community by Robert C. Martin and named the Single Responsibility Principle2 (SRP), which states that a class or component should “have only one reason to change.”

There has been a lot of discussion around the true size of a microservice. What can be considered “micro”? How many lines of code can it be and still be a microservice? These are the wrong questions. Instead, “micro” should refer to scope of responsibility, and the guiding principle here is the Unix philosophy of SRP: let it do one thing, and do it well.

If a service has only one single reason to exist, providing a single composable piece of functionality, business domains and responsibilities are not tangled. Each service can be made more generally useful, and the system as a whole is easier to scale, make resilient, understand, extend, and maintain.

Own Your State, Exclusively

Without privacy, there was no point in being an individual.

Jonathan Franzen, The Corrections

Up to this point, we have characterized microservices as a set of isolated services, each one with a single area of responsibility. This scheme forms the basis for being able to treat each service as a single unit that lives and dies in isolation—a prerequisite for resilience—and can be moved around in isolation—a prerequisite for elasticity (in which a system can react to changes in the input rate by increasing or decreasing the resources allocated to service these inputs).

Although this all sounds good, we are forgetting the elephant in the room: state.

Microservices are most often stateful components: they encapsulate state and behavior. Additionally, isolation most certainly applies to state and requires that you treat state and behavior as a single unit.

They need to own their state, exclusively.

This simple fact has huge implications. It means that data can be strongly consistent only within each service but never between services, for which we need to rely on eventual consistency and abandon transactional semantics. You must give up on the idea of a single database for all your data, normalized data, and joins across services (see Figure 1-1). This is a different world, one that requires a different way of thinking and the use of different designs and tools—something that we will discuss in depth later on in this report.

A monolith disguised as a microservice is still a monolith
Figure 1-1. A monolith disguised as a set of microservices is still a monolith

Stay Mobile, but Addressable

To move, to breathe, to fly, to float, To gain all while you give, To roam the roads of lands remote, To travel is to live.

H. C. Andersen

With the advent of cloud computing, virtualization, and Docker containers, we have a lot of power at our disposal to manage hardware resources efficiently. The problem is that none of these matter if our microservices and their underlying platform cannot make efficient use of them if they are statically locked into a specific topology or deployment scenario.

What we need are services that are mobile, allowing the system to be elastic and adapt dynamically to its usage patterns. Mobility is the possibility of moving services around at runtime while they are being used. This is needed for the services to stay oblivious to how the system is deployed and which topology it currently has—something that can (and often should) change dynamically.

Now that we have outlined the five essential traits of an individual microservice, we are ready to slay the monolith and put them to practice.

1 The Unix philosophy is described really well in the classic book The Art of Unix Programming by Eric Steven Raymond (Pearson Education).

2 For an in-depth discussion on the Single Responsibility Principle, see Robert C. Martin’s website The Principles of Object Oriented Design.

Get Reactive Microsystems now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.