Microservices… or die!
It appears that the microservice architectural style is dominating as the current hotness in how web applications are built. In many tweets and blogs it is being positioned as The One True Way to build web applications and as the antithesis to the dreaded Monolith. This has, unfortunately, resulted in a narrative along the lines of “if you aren't writing microservices you’ll be unable to support change or scale your application.” Frankly, this is crap.
I’ve seen the definition of monolith range from “an application which runs in a single executable that is scaled by running multiple instances” to “an application that has all of its parts interwoven and mingled”. The former would be a fine definition if one could embrace the monos (single) and suppress the lithos (stone). We could then stop at “application running in a single executable” and make no assumptions about the degree of modularity of the software inside that process.
It will be clear to anyone that has written any web software in the last twenty years that the quality of the software architecture within “a single logical executable” can vary greatly and many books cover this topic. We can have a conversation about all of the ways to partition the software within the executable to support change, scale, performance, etc. Is it partitioned to separate the presentation concerns from the business logic? Do the business functions have clear boundaries surrounding a small set of cohesive responsibilities? Is there a stable interface around places where we anticipate change? Can multiple teams work independently? All of these questions are relevant when discussing or building a web application. Unfortunately, it seems that in using the term monolith we are saying, “nope, no separation here, as our application is reminiscent of a large stone pillar”.
Things appear to have devolved to single executable = monolith = bad. With the mostly negative connotation of the label monolith, I fear we have engineers skipping past the difficult architectural trade-offs of progressively darkening the lines of separation and going straight to the independently flexible and immensely complex world of microservices.
Increased separation, at a cost
Migrating a module to “microservices” is one way to darken the boundaries between parts of your system by changing the communication from an in memory function call to a call -via some protocol- over the network. Separating a module from the application and interacting with it over the network allows for independence of technology, scale, error handling, change, deployability, etc.
This separation and independence is incredibly beneficial but comes at a cost: Things that were once accessed via an in memory function call (at the cost of nanoseconds) are now leveraged over the network (at the cost of milliseconds) and so performance is impacted. The network and separate (virtualized) machines can introduce performance and latency variability. Visibility and debugability can be impacted when leaving the confines of the single executable. Additionally, and perhaps most importantly, the ease with which the boundaries can be changed after you've gone over to an independent service is severely hindered.
One of the most difficult parts of creating good software is correctly identifying the responsibilities and separating them into independent cohesive segments. This is very hard to get right. It is something that should evolve as you learn more and the software gets used and modified. Usage will exert pressure on the application which in turn will highlight the flaws in the boundaries you've created. By darkening those boundaries first with a stable interface -with a good power to weight ratio (i.e. not too restrictive for the implementation and not so generalized that it is meaningless for clients)- you will be prepared to make the jump to microservices when needed.
Microservices are a just a tool in the toolbox for increasing the separation of business functions in your application. Microservices are not an all or nothing proposition. There is a lot of space in between crappy monolith and distributed microservice architecture. Perhaps one segment of your system warrants the separation but the rest of the application does not. Do you need increased separation for this module because the owner dev team needs to deploy independently? Do you need separate fault boundaries? Are the load characteristics of one business function so different from the rest that a separate scale boundary is needed?
In most cases, I think you will be well served by starting with a single “monolithic” executable and deferring microservices. First aim to understand the responsibilities of your application and create well defined boundaries that support change.
Microservices are great and I’m sure I’ll be writing applications that use them heavily in the future. But sometimes, microservices are just over-engineering.