Gianna has joined Avidoo Inc., a productivity platform, as a senior software engineer. In a kick-off meeting with the rest of her team, she brings up the subject of microservices and whether the team has adopted them in any way. She immediately gets a strong reaction.
“We have tried adopting microservices, but they don’t work”, Byron offers.
“It became a terrible mess!”, Kary adds.
Gianna blinked her eyes three times expecting some kind of elaboration, but none followed.
After an uncomfortable silence, Gianna asks: “So what happened?”
“At first it was great. Every time we were asked to create something new we had the opportunity to add a service and use whatever languages and frameworks we wanted to experiment with. We exposed REST APIs on systems it needed to collaborate with or worked on their databases directly. But after a while, things started to break more and more often and development slowed to a crawl.”
Gianna sighs. It sounds to her like her team had been building a distributed monolith, while what they had meant to build were microservices.
Distributed monoliths and other monstrosities
What Gianna has run into at Avidoo Inc., which is, of course, a fictional company, is unfortunately all too common. Drawn by the idea that microservices are a panacea, IT managers and engineers tend to skip identifying what advantages are a good fit with their organization.
People forget that there is no such thing as a free lunch. As well as advantages, well-built microservices architectures have tradeoffs. There are no “wrong” microservices, only microservices that do not deliver the advantages they were built for or pose unacceptable risks through their disadvantages.
Choosing to adopt microservices should start with deciding which of its advantages are a good fit for your organization. I will go over some of those advantages here, as well as cover an executive summary why they fail. Please stay tuned for the gritty details until my next post.
Increased team autonomy
Many companies organize teams around their member’s discipline or components. When creating real customer value this asks for a lot of coordination between teams and makes it next to impossible to work on one feature in parallel.
[caption id="attachment_24662" align="aligncenter" width="272"] Creating value with single-disciplinary team[/caption]
Microservices facilitate autonomy by covering one feature. Therefore a team can fully own it instead of it having to be owned by multiple teams. This helps to reduce cross-team coordination.
[caption id="attachment_24663" align="aligncenter" width="274"] Creating value with a multi-disciplinary team[/caption]
Greater fault tolerance
Where there is autonomy of a team there should also be the autonomy of a feature as explained in the previous segment. Features often depend on one another. In most environments communication is on-demand and pull-based, often over a REST interface. When this interaction is mission critical the service depending on this communication either has to have a sensible fall-back or it will, in turn, fail. This unhealthy pattern is often illustrated by system health checks that fail when one of its dependencies is unhealthy. Aside from causing deployment order to be difficult to manage, this is illustrative of a hard system dependency.
[caption id="attachment_24664" align="aligncenter" width="274"] Run-time dependencies[/caption]
Using the right software architectures like event sourcing, possibly complemented by CQRS it is possible to erase run-time dependency between most features completely. This is mainly due to the transition from a pull-based system to a push-based one.
Granular software lifecycle management
A common wish is to replace a certain feature within an application with a shiny new one. Either because requirements have diverged so far as to warrant a rewrite or development speed has been run into the ground by technical debt contracted by strenuous time-to-market demands. It might be naively thought that these should be replaceable as fast as it took to write them in the first place, but this mostly proves to be untrue. All too often replacing one feature results in having to make changes to many systems it depends on or vice versa.
[caption id="attachment_24666" align="aligncenter" width="480"] Lack of granular software lifecycle management[/caption]
By highly regulating inter-system communications it is possible to switch out one or more features completely without having to touch any of its dependent systems.
Flexible technology choices
Admittedly this is a tricky one. Onboarding and training people to switch to a common technology helps with inter-team mobility, driven by demand or personal interest, but reshuffling staff from several departments using different technology stacks they are frankly quite religious about can result in mass walk-outs.
[caption id="attachment_24665" align="aligncenter" width="480"] Forcing technology choices[/caption]
As long as the technologies can be integrated into your automated testing and deployment workflow a team’s technology choices can remain their own. Why change a winning team that’s united around their love for everything C# as long as they produce an artifact that adheres to your platform’s monitoring, logging and communication rules?
So why do they fail?
There isn’t one way to do microservices. Adopting microservices doesn’t fail because people don’t know how to do them but rather because they don’t remember what problems they were solving in the first place. As with any other decision adopting certain aspects of microservices come with a cost. Software architects tend to forget that they shouldn’t be helping their employer adopt microservices but should help them solve real business problems. Properly weighing the costs and benefits of these aspects against an organization’s need is of vital importance. None the less there is a default set of choices that can form a sensible inception from which to start this bold adventure. More on that in the next episode!