How to (not) lock yourself into architectural drawbacks, or, Microservice architecture as the reification of Pi-Calculus by Eduardo Bellani
Reification: the treatment of something abstract as a material or concrete thing, as in the following lines from Matthew Arnold’s poem “Dover Beach”: (Britannica 2018)
/The Sea of Faith
Was once, too, at the full, and round earth’s shore
Lay like the folds of a bright girdle furled./
The microservice architecture(MA) has taken a deep hold in the collective imagination of the software engineering community since at least 20141. This has happened despite serious and well documented drawbacks.
Such combination calls for an explanation. A sketch of such explanation is my intent here, starting with what MA is, at bottom. At a later date I intend to provide a history of how it came to be a dominant architecture on our industry.
My position is that MA is a reification(Britannica 2018) of abstract processes as defined by the Pi-Calculus (PC). Why do I think of that? Let’s start by defining things.
Definitions
PC is a model of message-based concurrent computation and its essential features are(Pierce 1997):
- focusing on interaction via communication rather than shared variables,
- describing concurrent systems using a small set of primitive operators and
- on deriving useful algebraic laws for manipulating expressions written using these operators.
More concretely:
…
π-calculus lets you represent processes, parallel composition of processes, synchronous communication between processes through channels, creation of fresh channels, replication of processes, and nondeterminism
…
A process is an abstraction of an independent thread of control. A channel is an abstraction of the communication link between two processes. Processes interact with each other by sending and receiving messages over channels. (Wing 2002)
Here is the best definition of the MA that I know of:
The microservice architecture pattern structures the system as a collection of independently deployable services that communicate only via messages through service interfaces. (Bass et al. 2021)
How are they mapped?
So here is PC is mapped to MA:
- Processes are Services(binaries loaded into memory),
- Channels are either queues or some form of APIs (RPC or REST),
- Messages are network calls,
- The avoidance of shared variables as an avoidance of a shared DBMS.
Conclusion
The reader might well ask, so what? Isn’t that a good thing? Well, no, it’s a very bad thing. Why?
Because once you mistake a formal entity such as the PC with a material one such as the MA, you lock yourself out of possibilities and in specific drawbacks that might not exist in other ways to implement the PC.
Let me illustrate the point here with a metaphor:
Say you think the formal entity called Boat
can only be implemented by
Yacht
. You now are locked into the design choices of that concrete
instantiation of the Boat
form. Transatlantic
or Canoe
are not
possible for you.
Coming back to the case at hand, here are some examples of how you could implement the pi-calculus without MA:
- Use a language/runtime that supports it, such as Erlang/ERT, SML/NJ or F#/.net.
- Use a framework such as Akka.
- Use a DBMS where several applications share the same DBMS but you use permissions and views to manage access and have logical independence.
Appendix: The Drawbacks
- It hurts performance. The overhead of serializing data and sending it across the network is increasingly becoming a bottleneck. When developers over-split their applications, these overheads compound.
- It hurts correctness. It is extremely challenging to reason about the interactions between every deployed version of every microservice. In a case study of over 100 catastrophic failures of eight widely used systems, two-thirds of failures were caused by the interactions between multiple versions of a system.
- It is hard to manage. Rather than having a single binary to build, test, and deploy, developers have to manage 𝑛 different binaries, each on their own release schedule. Running end-to-end tests with a local instance of the application becomes an engineering feat.
- It freezes APIs. Once a microservice establishes an API, it becomes hard to change without breaking the other services that consume the API. Legacy APIs linger around, and new APIs are patched on top.2
- It slows down application development. When making changes that affect multiple microservices, developers cannot implement and deploy the changes atomically. They have to carefully plan how to introduce the change across 𝑛 microservices with their own release schedules.
References
-
https://trends.google.com/trends/explore?date=all&geo=US&q=microservices ↩︎
-
In a certain sense, all externally facing APIs face this. Microservices just make this problem worse by making everything
external
and not part of the same checkable runtime. ↩︎