By Arvind Jayaprakash, Principal Architect, CTO Office
Enough and more articles have been written around the virtues of microservices and how one can convert a monolith into a microservices. An order of magnitude lesser amount of articles have been written around some of the hassles that microservices introduces. A far fewer number of articles have been written around how do you assess if a transition from a monolith to a microservice is going to be a helpful or a harmful decision.
This article provides some indicators that you can use to assess how such a move might play out for you. The short answer is that there is no universally correct answer, so read on.
DOING MICROSERVICES WRONGLY: THE BIG ONES
Were you a SOA to begin with?
One of the most common problems is that the monolith in question wasn’t following the SOA model. Here is what reality looks like:
The scope of the rewrite is limited to just what is within the dotted box along with the interactions represented by the green arrows. What gets missed out in the transition is accounting for the interactions represented by the red arrows. One usually wishes it away by saying “not in scope”. Such an approach is disastrous as it would result in an incomplete transformation. The next section touches upon a related topic and brings out the actual problems that are caused when we miss out the database interactions.
Microservice v/s SOA
Microservice has come to mean a small enough executable that is most likely packaged as a docker container. However, it is important to not lose sight of the fact that microservices succeed only if they follow the general principles of SOA. This is best represented by the following set of illustrations:
It is trivial to randomly slice the application code into two or more pieces, package them as different executables and then deploy them as such. What is hard is to segregate the business objects that carry state (and backed by some form of a database/data-store) into disjoint subsets such that each of your microservices is able to solely manipulate these business objects.
Failure to do so (as seen in Figure 2) causes maintainability issues down the line wherein
- The best thing to happen is one of the microservices breaking due to a schema incompatible change that was released along with another microservice
- The worse (and more common) thing that happens is invariants that form a part of the code in each microservices break in a silent manner that gets discovered in production by virtue of some hard to explain bugs
Note that Figure (3) and Figure (4) are not the same. What now looks like 1:1 mapping of a microservice to a DB is not the same as a microservice logically subsuming a DB.
The most effective way I’ve found to perform the SOA test on designers of microservices is to see if they are able to represent their component design using the style described in Figure 3.
CYCLICAL DATA SYNC
Once you are able to design your systems in line with Figure 3 as opposed to say Figure 2, the next challenge is to check for true loose coupling of DB-X and DB-Y. One often has certain entities/business objects present in both these databases. This in itself is not a problem. However, the following situations are problematic:
- Entity γ is present in both DB-X and DB-Y, and, is modifiable via service 1 and service 3 respectively. This opens up the possibility for conflicting updates. Note that DB level conflicts is not the troublesome piece here; the domain specific entity level conflicts is. Having a DB level conflict resolution logic is no good as the conflicts are logical and perceptible only when we look at both the databases as a single database containing the same entity
- Entity β and γ are present in both DB-X and DB-Y, and Entity β is modifiable only from service 1, and Entity γ is modifiable only from service 3, but the validation rules (invariants) of entity γ rely on the state of entity β. Such a dependency again creates opportunities for inconsistencies as
- Entity γ is unable to enforce referential integrity of type “RESTRICT” onto Entity β. Referential integrity should be interpreted as entity:entity invariants as opposed to narrowly interpreting it in the relational database parlance
- The state syncup between DB-X and DB-Y would be some non-atomic (read: eventually consistent) operation given the decoupling of the two systems. This can cause referential integrity issues during updates of γ as it could be looking at stale copies of β
What is your (new) macroservice?
The notion of a macroservice is not a parody on microservices. It is a very real issue that gets lost as no one speaks about it. The monolith changing to a microservice should be seen as an internal refactoring/modularization. This means that inter-module communication should not be confused for external communication. Here is yet another picture to illustrate the question that is being posed:
The communication that is happening from external services to macro-service i.e. whatever was standing in place prior to the introduction of the microservices needs to be well understood as being a distinct API that should not be confused with the APIs that the microservices themselves use to talk to each other.
Note that the macro-service is not an actual component. Instead, it is just an enumeration of interfaces that are deemed worthy enough to be consumed within the larger context that these services reside in. One can choose to implement an application firewall at a macro-service level to enforce the discipline of identifying what qualifies as externally usable interfaces. This reduction/distillation of the combined surface area of the interfaces of the microservices is needed in larger organizations to keep the overall org-level service count (and it’s associated APIs) to a low enough level that people don’t get lost in all of the internal interfaces.
Other mundane issues
There are a whole bunch of other problems that creep up when choosing to go microservices over a monolithic application. A few examples are long tail latencies introduces by callout graphs that span across multiple microservices as part of handling a single end user request, distributed logging/tracing, producer/consumer API compatibility changes, etc. etc. These, however, can be tamed by choosing the right set of development and operational tools. Getting these “other things” right doesn’t need to happen on day 1, but the sooner one gets these right, the better it is.
WHEN NOT TO DO MICROSERVICES
The simplest rule is as follows: decompose your overall application only until the point wherein you are able to not commit the fatal sins called above that threaten the integrity of your entity state (data). If you feel you are already at such a point with your “monolith”, then don’t try to decompose it any further.
Engineers often tie themselves into knots by choosing to disregard the above rule of safety. If you really need to go down the microservices path and you do run into this wall, then it is time to actually change your product behavior in a manner that lets you maintain data correctness. Which then brings us to the question …
WHEN/WHY SHOULD I DO MICROSERVICES AT ALL?
The truism of microservices is that they are supposed to increase the speed of product development. So what slows things down in a monolith? It happens to be time spent in “critical sections” of the software development cycle that limits “concurrency” of development that can happen on a single codebase. These critical sections manifest in various forms such as a need for an all-knowing soul to act as a reviewer for merging changes from feature branches onto mainline, the unavailability of test infrastructure to test out the various concurrently developed features at the same point in time, a single test run taking way too long to execute and thus compounding the previous problem, and so on.
If you aren’t experiencing “contention” as part of your product development process, then converting your monolith into a microservice will not help increase productivity. I’d go so far as to say that if you are starting a new initiative, don’t prematurely optimize for a contention that might happen down the road and hence prefer to keep the service count to a small value
BONUS READ: BOGUS REASONS TO DO MICROSERVICES
While the fad of microservices is no longer at its peak, people ever often pursue it for no good reason. Sometimes, it is just a matter of pushing a different agenda under what is an easy sell. Other times, it tends to be harmful. Here is what I have seen
Harmless reasons with ulterior motives
“We can go polyglot on storage technologies, programming languages, etc. etc.”
People here often want to just start using a new technology outright but it is a far easier sell to the position as “will be tried only in some places a.k.a. one of the many microservices”. If trying out newer technologies is an explicit goal, then go with microservices by all means. However, do understand the invisible taxes it imposes on your design.
Outright harmful (ill-informed) reasons
“Our ability to reason about the code will increase”
This is true only if you are able to decompose the application in safe manner as described above. What often happens is that people want to do a rewrite as they can no longer reason about an aging code. A pure rewrite usually doesn’t solve this problem. One needs to alter the product behavior to reduce the degree of coupling that exists between various entities if we have to regain control of the situation. The conversion from monoliths to microservices can play out as follows:
- Happy case: the high degree of [unnecessary] coupling that exists across entities is identified, the product behavior is altered to make things more manageable and the codebase is refactored into microservices for good measure to ensure that the problem doesn’t unconsciously creep back in
- Sad case: People fail to notice the high degree of coupling that exists between the various entities, undertake the “monolith to microservice” rewrite to only realize very late that they are having to break the strong SOA properties to bring back the functionality. You are now left with a codebase that is a lot more bug prone than before; let alone the time lost in the rewrite
More credit than deserved
The ability to horizontally scale a product far more easily is also an oft-cited benefit of moving to a microservices architecture. This is a bit of a red herring as horizontal scalability in itself has nothing to do with microservices. A well-designed monolith can be made to horizontally scale as easily as it is possible with a microservice. The common problem is that the monolith in question tends to be “legacy code” that was never written with horizontal scalability in mind.
Things truly change only when the monolith provides functionality with high variance in resource needs i.e. one flow is very CPU intensive v/s say, another flow being very memory intensive. One gets lucky with microservices if the split happens such a way that these all flows in each microservice are solely CPU bound v/s memory bound v/s IO bound (and so on). Such a scenario ends up being a happy coincidence and typically cannot be engineered for.
Moving over to a microservices should be primarily seen as a way to move towards “wait-free” development within a team. This benefit relies on faithfully following the SOA model for each microservice that one comes up with. Consequently, microservices within an organization are hostage to Conway’s Law and hence one should not be surprised if the microservices undergo fragmentation and reassembly in response to changing org structures. Take your plunge into converting monoliths to microservices only after you understand when it actually pays off and also what could cause the service count to change.
Disclaimer: This document is intended for the internal use and information purposes only and may not be distributed externally or reproduced for external distribution in any form. Any views or opinions presented are solely those of the author and do not necessarily represent those of InMobi Group. InMobi, makes no representation or warranty, express or implied, as to the fairness, accuracy, completeness or correctness of the content of the document and InMobi does not accept any responsibility or liability whatsoever for any loss or damage however arising from any use of this document or its contents or arising in connection with it.