Imperfect #14: How to map dependencies
Avoiding the distributed monolith antipattern requires great situational awareness. Today we go over what that means, how architecture reviews can help, and one simple way to anticipate dependencies.
Last week I wrote about how to break distributed monoliths: microservices architectures full of leaky boundaries where services depend deeply on one another. Teams maintaining such architectures find themselves unable to make progress.
Csaba from Leadership Garden summarised it perfectly:
The distributed monolith is an antipattern where microservices architecture teams become entangled in a thick network of dependencies, leading to delayed deliveries and decreased customer satisfaction.
My framework for solving this problem is a checklist that tries to find a way for the team who’s sending work across boundaries to avoid or own it instead.
I added that, as an engineering leader, working closely with Product and business stakeholders as early as possible, is key.
However, that article makes two assumptions:
You know which dependencies exist
You and your Product peers follow some kind of process
Sadly, that’s just not always true. Luckily, the first one is easy to fix. We’ll see how today.
Architecture and spreadsheets
Knowing the architecture of the services you’re responsible for is essential. One thing I like to do is periodically run architecture reviews team by team, and compile them in a Miro board. I basically sit down with the team, open a Miro board, and ask them to describe their systems to me, while I draw them. This takes time but is much more effective than having a diagram delivered to me. It’s like pair programming but for architecture. Pair architecting?
Some dependencies can be known simply by looking at those architecture diagrams. Whenever two services are connected - either by an event, an API call or a database - that connection is a candidate. By the way, if they connect through a database, there might be a problem there.
A well designed system actually leverages events and APIs to decouple things. At least initially. But time and entropy conspire to develop leaks in those connections. Things like adding a field to a model in an internal API just to enable a specific service.
Knowing how services interconnect lets us keep a closer watch on those interfaces, and monitor them for change requests. Those change requests are precisely what we want to avoid.
One thing I don’t like to do, but always find eye opening, is keeping a simple spreadsheet of dependencies. Its columns can be as few as:
team
sprint / project / initiative / whatever you call your iterations
issue tracker link
teams this depends on
are we asking for changes upstream?
nature of the change
Could this all come from the issue tracker? Yes. Are mine always set up and maintained well enough that I can trust this data to be there without fail? Hahahahaha No.
So it’s useful to sit down with my EMs and our Product peers periodically, look at the medium-term roadmap, and make sure we flag those dependencies on the sheet. There are often surprises there, especially in larger organisations.
After that, we’re ready to apply the framework I laid out in my previous post and try to chase the dream of true autonomy.
Conclusion
Situational awareness is key to prevent dependency hell. Keeping a good handle on the real architecture of the services our teams maintain - not the imagined utopia someone wrote on the wiki two years ago - helps us anticipate where dependencies are likely to appear.
Regularly reviewing the upcoming work to flag possible new dependencies creates the opportunity to have a principled discussion about how to either prevent them, or accept them deliberately.
Thanks for reading and Happy Friday!
PS: I’m thinking of changing the name of this newsletter. Imperfect is not great branding to be honest. More on this soon.