Microservices and the Monolith Karma 

Microservices and the Monolith Karma 

What is holding you back from implementing microservices? In this article, our PM Diego De Sogos tackles the main risks when deciding to move towards microservices. Take a look!

When I started writing about microservices back in 2015, they were gaining reputation and starting to be quickly adopted by the enterprise community. In a few years, microservices have shifted from the new kids on the block to becoming the de facto architectural style in most industries and business areas. In 2022, microservices are everywhere.  

What began as just another architectural design suddenly became the only feasible architecture, and the IT and business worlds were tricked, again, into thinking they had found their silver bullet, the one ring to rule them all. 

The popularity trick worked so well that we ended up with a ridiculously huge number of projects that were carried over with microservices, even if their engineers knew how to implement them correctly or what’s even worse, if the problem to be solved would really fit into a microservices architecture at all.  

Am I exaggerating? Look at this example from the research done by RedHat in 2021 about the main driver that increased container-based solutions. A whopping 40% of people in the survey indicated that the main motivation to switch to containers was “career progression”. 

So, what is holding you back from implementing microservices? 

Ok, developers and architects may have some CV bias when opting for microservices, but what’s wrong with using microservices anyway?

Microservices and the monolith karma

Microservices have so many advantages, so what can go wrong if we use them for every single project that comes along? Some years ago, I pointed out that many of the problems when implementing microservices were related to the underestimated complexity of the systems and the expertise of the people building those systems. Over the years, tooling and frameworks like SpringBoot have advanced enough to reduce the impact of those two main issues.  

Nowadays, following the guidelines and rules of any advanced framework, you can end up building a microservice very quickly. Rest assured it will work as expected under most conditions (the framework will guarantee the performance, security model, logging, tracing, and many other technological challenges imposed by the architecture). However, being capable of building microservices the proper way is not enough.

Next, I’ll go a bit deeper into what I think remain as big risks when deciding to move towards microservices, namely the distributed nature of the system and the problem of building truly decoupled services. Both things are related to one single concept which is what sets microservices apart — monolithic design

Artificially distributed does not mean over-complicated 

There are thousands of articles explaining the risks and problems you will face when dealing with distributed systems. Distributed systems are complex, and microservices are inherently distributed. Does the system you need to build have to be that complex? Or what’s more important, is there a natural way of decomposing your problem into a distributed system? 

If you don’t find a way to truly split the system into distributed components due to the nature of the problem, by implementing microservices you will end up with an artificially built, distributed system. And let me emphasize the first paragraph again: distributed systems are harder to reason about. Even if you implement microservices correctly, following all best practices, you can end up with a very complex system. 

When you start a new project, you must pay attention to the problem itself and the goals pursued. If you hear the word microservices and scalability very often from the very beginning of the discussion about requirements and the search for a solution, beware of the bias towards choosing architecture based on what everyone else is doing.

Microservices should not be the goal or purpose of your project, as they are just the means. You have a problem, you want to transform or update an existing system, and after thinking a lot about what the benefits would be from the transformation, you may find out microservices provide the means to fulfill those goals. 

The key here is to determine the nature of the system to be built. Some systems are inherently transactional, and the advantages of a distributed architecture with microservices aren’t enough to justify the artificial creation of distributed services. For a domain with lots of computations being centralized, stick to a single modularized domain (around reusable components) architecture (instead of emulating the monolith on top of a distributed system). 

Thinking of microservices as a means instead of the goal will help moderate its usage and overall reduce the complexity of creating a fully distributed system when not needed. 

Distributed does not imply decoupled 

Related to what I explained above about artificially decomposing your domain into microservices is this notion of truly decoupling the involved services. It is harder than you’d think. 

Even if the business domain and the problem being tackled can fit naturally into a vision of dozens of services composing a distributed system, keeping clean and well-defined boundaries across the services is very hard.  

Unless you define and control very strictly (and explicitly) the dependencies across your different APIs, it’s likely you will end up with distributed deployable components (in terms of binary dependencies) that if you attempt to deploy separately, you will break your entire system in runtime. That is, creating distributed components does not guarantee any kind of independence and loose coupling between them. 

So, what do you do to create really loosely coupled systems? You need to attack both explicit binary dependencies as well as implicit contracts among the APIs that your services will expose and consume. 

Explicit dependencies are easier to solve. Most microservices implementers tend to cut dependencies on shared libs by copy-pasting lots of data models across different code repositories (forget about DRY principle!). Besides the drawback of reusing code and the painful tasks of future refactorings, you would need to go all the way down to the domain schema. Otherwise, the coupling will still be there.  

Microservices and the monolith karma

Domain Driven Design principles become key to minimize dependencies and coupling. Determining API boundaries based on domain boundaries instead of technical boundaries will allow you to create smaller and easier APIs for your microservices. However, determining the API boundaries and documenting them may not be enough. 

API docs may not address the problem with implicit dependencies between them because of assumptions made by development teams on both sides (consumer and producer). Most teams work this out by generating a Swagger documentation, and then, they split up working on their clients and services using their own mock artifacts as part of the development (the client’s team creates mocks of the services, and the service team creates their own API tests using tools such as Postman to test the API).

Typically, this approach will be OK if both teams fully understand the API and make the same assumptions about those implicit things not stated in the documentation, which is unlikely to happen for most real-world scenarios (besides some trivial APIs).  

A better approach to solving this API dependency problem and inconsistency is applying consumer-based contract testing. It allows producers and consumers to work through an executable contract that once defined can serve as a mock service for the clients and API tests for the service implementers. 

To sum up 

Microservices are mature enough that they have become the main architectural style pursued by the majority of companies. Although most technical complexities are now taken on by mature microservices frameworks, the issues related to the intrinsic nature of building a distributed system remain. Understanding the distributed nature of a problem is key to escaping building a monolith in disguise.  

Think of microservices as a means instead of a goal. In the design and implementation phase, pay close attention to implicit coupling that may be happening between your microservices. If you can’t deploy a microservice at any moment and have to wait for some library dependencies, or any other microservice to be deployed, that is a big red flag.

Comments?  Contact us  for more information. We’ll quickly get back to you with the information you need.