By reading this article you'll learn that ... orchestra (like Emperor) is naked, easy problems sometimes get far more sexy solutions than the hard ones (so screw those), SOA is not dead, microservices in the wild tend to be grotesque & out-scaling can be achieved in several different ways.
First of all, thanks for reading this :) I'm quite sure that at least 3/4 of potential readers have dropped off after seeing the word "microservice". And ... I don't blame them at all, I'm fed up as well. That's one of the reasons why I've written this article.
Hello (microservice) World.
My main problem with microservices is that there are tons of materials on them on the web, but in fact, a vast majority is pretty much worthless - covers only the most basic & common truths, without even touching the actual essence of complexity & real challenges related to using this pattern in your applications.
The usual microservices story is about code & data isolation, open standards-compliant contracts, independent deployment, backward compatible or semantically versioned API, sometimes about DDD basics like bounded context & aggregates. And that's pretty much all.
Real world calling
What all these articles miss (intentionally are not) is the hard part, e.g.:
- reads are synchronous by default - how to keep them decoupled (e.g. from deployment perspective)? or maybe give up on that?
- majority of end-user-level interactions cross aggregate boundaries (involve several transactions) - how to orchestrate them (especially if happy path fails ...)? DTC? stateful sagas? per-operation (stateless) compensation logic?
- what are the pros/cons of orchestrator-based & choreography-based sagas - which one to use under what circumstances?
- how to set up a "floating" deployment in case of multi-versioned endpoints? and in case of single-versioned endpoints?
- are there any patterns for frictionless feature toggle management? development ones? deployment ones?
- how to operationalize distributed state mgmt? to cover saga data coherency? to handle backups/restores?
- are there any best practices for dealing with user-repeated operations (in case of subsequent failures) with stateful sagas?
- what are the advantages/disadvantages of various node discovery approaches (client-side VS server-side, deploy-time VS startup-time VS run-time)?
- is tiering the services an actual pattern or an anti-pattern?
- what are the viable alternatives for microservices if exponential growth leaves you no other option but out-scaling?
In my case - I had to learn all of these (& many more) the hard way: by trying out ideas & learning from mistakes. I find that amusing as some of the problems depicted by these questions are much harder than e.g. the ones tackled by (wildly popular these days) container orchestrators - "sold" usually as silver bullets in the world of microservices ...
In the mean-time we (IT professionals) have found ourselves yet another distractor - serverless functions (as a deployment unit) - which, however useful, also focus on secondary problems (IMHO).
Oldie but goldie
What's even more amusing - (almost) all attempts to solve the really painful problems in microservice architectures seem to closely follow widely condemned & despised old paths:
- asynchronous, decoupled, message-driven communication is deceptively reminiscent of old-fashioned Enterprise Service Bus(es)
- cross-microservice spanning sagas are not that far from the criticised concept of SOA orchestrators like Microsoft BizTalk or IBM WebSphere Integration Bus
Microservices (?) in the wild
Having observed all of that, I couldn't resist diving deeper - I've put the detective hat on & started the investigation aimed to find out how do the companies (I have any access to) apply the concept of microservices in their solutions. It wasn't hard to identify some regularities & patterns. Apart from just a few cases of companies that had managed to properly implement the idea of microservices (which was itself a tremendous effort & cost), there are basically 3 separate, much more numerous groups:
- faking ones - low hanging fruit gatherers, who've just split their service layer (or equivalent) into several separate processes that communicate with each other (usually synchronously, because otherwise it would require much more serious architecture revamp); they have still one huge OLTP relational DB (in many cases used for reporting as well); clearly - their journey brings them pretty much no gain, but they don't care, well - they do have "microservices", that was the goal!
- shattering ones - these ones went 1 step further than the faking ones, as they've also split the data among the services - so as a result, they have a constellation of tightly (synchronously) coupled inter-communicated endpoints where every change has an insane overhead; these ones usually over-use API gateways and/or BFF pattern to separate services into higher- & lower- level services; the benefit is disputable, while the price (due to accidental complexity) is nuts
- lost ones - the road to hell is paved with good intentions, so these ones had good intentions but they've got completely lost on their way - they are in permanent turmoil between their monolith legacy & sorta "microservices", too early to earn any gain, too late to avoid the excessive cost, too tired/stretched to make a firm step further towards (non-existent) architectural to-be vision
Let's get real
OK, so many are failing, other ones pay a high price for a potentially high benefit. But what's my point here? Is it just about emphasizing that there are much more challenges in microservice adoption than it may seem at the first glance?
No, my point is that in our chase for over-hyped half-truths, we recklessly tend to ignore alternative solutions to the very same problem (of the desired out-scalability) that do not get as much attention as they deserve.
Which ones? Here are few examples (that also can be combined):
- horizontal sharding - distribute OLTP processing by some key business entity (client, contract, agreement, area, industry - whatever makes sense in your domain)
- CQRS - write & read operations have separate transaction scopes (aggregates) - the only challenge is to have a reliable, event-driven change propagation mechanism (between write & read models), but this seems like a significantly simpler problem
- modularized monolith - yes, it's really not that stupid - if you enforce aggregate boundaries within monolith, you can gain significant performance improvements thanks to as simple techniques as basic DB partitioning, while at the same time minimizing latencies & minimizing negative effects of synchronous operations; using "microservice lingo" - isolating chunk of the bounded context to separate library may not be significantly worse than to separate process
Don't fall into the trap of blindly applying everything you've read in a random blog post on the web. Think for yourself. Focus on your case/domain. Evaluate the real cost against potential, feasible benefit.
That's how the engineers are supposed to act.