Isolate All the Things
Note: Without great solitude, no serious work is possible.
Failure isolation — to contain and manage failure without having it cascade throughout the services participating in the workflow.
Example:The ship is divided into distinct and completely isolated watertight compartments, so that if compartments are filled up with water, the leak does not spread and the ship can continue to function and reach its destination.
Resilience — the ability to heal from failure — depends on compartmentalization and containment of failure, and can only be achieved by breaking free from the strong coupling of synchronous communication.
Do One Thing, and Do It Well
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 only has one single reason to exist, providing a single composable piece of functionality, then 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
Note: Without privacy there was no point in being an individual.
A service can be treated as a single unit — including its state and behavior — and in order to do that each service needs to own its state, exclusively. This includes not allowing one service to call directly into the persistent storage of another service, but only through its API — something that might be hard to enforce programmatically and therefore needs to be done using conventions, policies and code reviews.
Embrace Asynchronous Message-Passing
Communication between Microservices needs to be based on Asynchronous Message-Passing. An asynchronous boundary between services is necessary in order to decouple them, and their communication flow, in time — allowing concurrency — and in space — allowing distribution and mobility. Without this decoupling it is impossible to reach the level of compartmentalization and containment needed for isolation and resilience.
Another benefit of asynchronous message-passing is that it tends to shift focus to the workflow and communication patterns in the application and helps you think in terms of collaboration — how data flows between the different services, their protocols, and interaction patterns.
It is unfortunate that REST is widely considered as the default Microservice communication protocol. It’s important to understand that REST is most often synchronous which makes it a very unfitting default protocol for inter-service communication. REST might be a reasonable option when there will only ever be a handful of services, or in situations between specific tightly coupled services. But use it sparingly, outside the regular request/response cycle, knowing that it is always at the expense of decoupling, system evolution, scale and availability.
The need for asynchronous message-passing does not only include responding to individual messages or requests, but also to continuous streams of messages, potentially unbounded streams. Over the past few years the streaming landscape has exploded in terms of both products and definitions of what streaming really means.
The fundamental shift is that we’ve moved from “data at rest” to “data in motion.” The data used to be offline and now it’s online. Applications today need to react to changes in data in close to real time — when it happens — to perform continuous queries or aggregations of inbound data and feed it — in real time — back into the application to affect the way it is operating.
We are moving away from the traditional batch-oriented architecture altogether towards pure stream processing architecture.
Stay Mobile, but Addressable
First, addresses need to be stable in the sense that they can be used to refer to the service indefinitely, regardless of where it is currently located. This should hold true if the service is running, has been stopped, is suspended, is being upgraded, has crashed, and so on. The address should always work.This means a client can always send messages to an address. In practice they might sometimes be queued up, resubmitted, delegated, logged, or sent to a dead letter queue.
Second, an address needs to be virtual in the sense that it can, and often does, represent not just one, but a whole set of runtime instances that together defines the service.
Load-balancing between instances of a stateless service.
Active-Passive state replication between instances of a stateful service.
Relocation of a stateful service.