Rule of Twos and Microservice Architecture
Lessons Learned in Avoiding Dependency Lock-Ins
Microservice Architecture is fundamentally all about avoiding the need for various kinds of coordination in a complex system. It can be shown that all other often-cited microservices principles are essentially derivatives of this fundamental goal. One such important principle is avoiding shared libraries and binary coupling. If you haven’t already, you should watch Ben Christensen’s wonderful talk on the subject. Ben does a great job explaining the challenges shared libraries introduce for a Microservice Architecture.
The decision to share binary libraries, frameworks, or platforms can lead to a gargantuan challenge when you eventually need to upgrade or replace those artifacts in just one part of the product or organization. A task that should take a day or a short scrum cycle, at most, often turns into a multiple-months (sometimes years) long project due to the complexity of coordinating the effort across multiple teams. As a matter of fact, such efforts are often entirely cancelled when stakeholders realize the magnitude of implications.
It’s easy to follow and buy into this logic. However, it can be much harder to come to terms with its negatively disruptive implications. If sharing libraries across microservices is indeed risky, can we still hold “reuse of code” as one of the most desired practices in all of software engineering? What about leveraging open-source libraries? This line of questioning established beliefs can get quite frustrating as we go further down the rabbit hole. How many closely-held engineering principles will Microservice Architecture take a swing at? Is this architectural style even worth it if it’s going to fight and rebel against every. single. principle. that we software engineers have come to appreciate?
I’m of course being hyperbolic, but let’s pause for a second before we either reject Microservices wholesale or throw away everything we believed in prior to Microservices. The reality is — Microservice Architecture is not against sharing of libraries per se. What it is actually against is creating death-grip dependencies that would compromise our ability to practice the principle of Avoiding Coordination.
While the sharing of libraries can often lead to significant challenges — it doesn’t have to. And sharing of libraries is not the only way to compromise Avoidance of Coordination, either. You can just as easily create costly lock-ins by choosing a single media type (e.g. JSON-LD, HAL, Siren or UBER) for all your APIs. Or you can choose a single way of accomplishing fault-tolerance that can lead to the same implications. The examples are many.
Fortunately, we can use a fairly simple practice which can help you avoid all kinds of dependency lock-ins while implementing Microservice Architecture. This principle was inspired by many conversations with my dear friend and renown expert of distributed systems Mike Amundsen. I have since been able to use this principle on multiple projects for previous employers with great success. I lovingly call this principle Rule of Twos and have come to appreciate it a great deal.
Rule of Twos
The basic idea is that your choices won’t lock you-in if you constantly exercise your ability to support multiple choices:
For any critical component in your system, make sure you use at least two alternatives, in production, at the same time. Even when you only need one. Also, make sure you have the infrastructure to support the two alternatives as easily as you would for a single one.
In practical terms, this principle has lead me to implement some additional general guidelines in how I approach Microservice Architecture such as:
- Make sure some critical APIs are written in Go or Scala, even if most of them are written in Node. Standardization on a single solution can be efficient, but we need to balance efficiency with robustness.
- Make sure some microservices use a document/NoSQL database, even if most of them use a traditional, relational one. In making this happen, you will develop capabilities that will be invaluable for you in the long-term.
- Make sure some critical APIs are communicating with a media type that is different from the one being used everywhere else. For instance, if you standardize on HAL, use UBER for some other, important APIs. In making such multiple-message-format support happen, you may end up implementing a powerful design pattern such as Representor that I find makes your APIs long-lasting, evolvable and adaptable.
I won’t bore you with more examples. I know you can find a lot of them in your own contexts. The main point I would like to communicate is — sharing of libraries or standardizing on other organizational choices is not always necessarily toxic. The dangerous part is not having a flexible infrastructure to accommodate any non-standard alternatives.
Most importantly — you will not be able to support such alternatives unless you practice your openness to choices every single day! If you require robustness, you need to practice it and not just theoretically design for it.
I’ve found a great way to avoid this by implementing the Rule of Twos in my work. Even if you don’t need alternatives today, you should implement examples of them today. That way tomorrow, when you actually need it, you will be free to exercise choice. Always implement critical decisions as two alternatives, from day one, and lay down infrastructure to seamlessly support them — that is the essence of the Rule of Twos and my personal guideline for avoiding dependency lock-ins.
DISCLOSURE STATEMENT: These opinions are those of the author. Unless noted otherwise in this post, Capital One is not affiliated with, nor is it endorsed by, any of the companies mentioned. All trademarks and other intellectual property used or displayed are the ownership of their respective owners. This article is © 2017 Capital One.
For more on APIs, open source, community events, and developer culture at Capital One, visit DevExchange, our one-stop developer portal. developer.capitalone.com/