Breakable Monoliths and the Law of Demeter
Keyboards are on fire: you’re cranking code for your new and awesome technology start-up. Nothing can stop you, not even this arbitrary limitation of 24 hours per day: your MVP is coming hot and heavy. You know exactly what you want to build, you even have your toolbox of choice handy, but there’s still a nagging doubt: should you build your MVP as a monolith or a set of distributed micro-services?
Trust us on this: build a monolith.
But, and that’s a serious caveat, make it breakable from day one. If you don’t do so, the wrath of Demeter will be on you.
Monolithic applications are easy to build and simple to grasp because everything is in one place. The drawback is that, because everything is in one place, maintaining a monolith in the long run becomes problematic. As the code base grows, it starts crumbling under its own weight and the outcome is rarely a new and pretty star in the sky, but more likely a black hole that no developer dares approaching. Moreover, monolithic applications do not scale gracefully because it isn’t possible to scale technical or business functions independently: it’s an all or nothing situation, which may be satisfying for cloud providers (as they can sell you more larger VM instances), but is not practical from the technical and bottom-line perspectives.
One could be tempted to skip the monolith altogether and start with a distributed application. Speaking from experience, that would be a very bad mistake. Distributed applications are not only more complex to build than their monolithic counterparts, they are also much more difficult to operate. On top of needing to handle failure of the distributed components, solid practices in term of distributed event traceability are needed from day one. You should not have to deal with such constraints during the MVP build-up phase.
So is there hope or are monoliths rewrites poised to always be long and painful exercises? I believe there’s hope, again from personal experience, and that it’s possible to build a monolith that will break easily and become distributed when the time will be right.
There are multiple strategies for building a breakable monolith, but in this blog and for the sake of conciseness, I posit that abiding to the Law of Demeter would take you a long way in the right direction. Promiscuity in monolithic applications leads to all sorts of coding problems: the principles behind this law help mitigating these issues.
The Law of Demeter (aka the principle of least knowledge) can be summarized in three sentences:
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
- Each unit should only talk to its friends; don’t talk to strangers.
- Only talk to your immediate friends.
What does it look like in practice? Here are a few points derived from this law:
- Avoid domain level porosity – Have clear boundaries between the main domains of your business and avoid low level interactions between them. For example, an ORM will happily let you navigate a graph of objects across multiple business domains, which will eventually create strong touch points at the database level, resisting future breaking.
- Keep data and logic separated – Encapsulate logic in a set of internal services behind a clean API. Keep your data representations dumb: this is easy in functional languages, but harder in object oriented programming. For example, for the latter, resist adding business logic in DB-bound objects but instead locate this logic inside your internal service layer.
- Identify and functionally disconnect sub-systems – Avoid having sub-systems know about the internals of others, but instead use canonical messages. For example, if you integrate a 3rd party service for sending transactional emails, the parts of your application that need to send emails should deal with a data structure (object, map…) that is devoid of any terminology or concept that is specific to the particular 3rd party service chosen. Instead they should deal with a canonical representation of what an email is and let the internal service dealing with the 3rd party integration translate that canonical form into the specific form.
- Identify and timely disconnect sub-systems – External integration points that can be slow or fail, long running internal operations, etc. are all potential delimiters for sub-systems. Disconnect them and tie them up with “dumb pipes”. Building on the previous example, route this canonical email representation via an in-memory message queue to the actual email sender. Alternatively, use a message queue broker collocated with the monolith.
- Resist anti-patterns from your tooling – Be wary of the all-encompassing frameworks that promise easiness at the expense of simplicity. Use only the good parts of your tool-kit, rejecting any that either explicitly or implicitly induce you to violate the Law of Demeter and guide you into building highly cohesive unbreakable monoliths.
This list is not exhaustive: in fact, the best exercise for you would be to look at your code base and start identifying were the Law of Demeter is violated. The more you’ll keep these principles in mind,
the easier it will become for you to build a monolith that will break easily.
Do you have other strategies for ensuring a monolith will be breakable? Please do share them in the comment section below.
– David Dossot
Director of Software Architecture