Breaking the Microservices Myth: When Monoliths Make Sense
Do you know Amazon Prime Video has shifted part of its architecture from a micro-service to a monolithic approach? Specifically, they moved their video monitoring service to a monolithic architecture for better efficiency and cost optimization. However, other parts of their system still use microservices.
But you may wonder, isn't a micro-service architecture a one-stop solution based on recent era communications?
Just like a monolithic architecture, a micro-service architecture also comes with a set of problems that we need to deal with. Below are a few listed:
Network calls in communication between services:
Whenever a service wants to communicate with another one synchronously, a network call (an HTTP request) should happen. You might argue:
We can hold the services in the same private network (VPC in terms of AWS), which can drastically decrease the latency. But still, there will be additional latency added.
Code-wise, since the re-incarnation of RPC frameworks like (gRPC), the code to be written is very low. But actually, the code is not visible to the developer's eyes; there is much more going on inside (like request and response blob transformation and creation).
In a monolith, these problems would not happen, as it is just a method call away.
Maintaining the services:
Let's assume there are 10-12 micro-services in a system.
If there is any common dependency upgrade, the same operation needs to be performed in all services. A simple example can be JDK upgrades.
We need to have a separate set of resources to maintain the deployments of individual services.
We need to have separate logging mechanisms and resources (like CloudWatch log groups/metrics).
In a monolith, you need to do all the above only once.
Unnecessary cascading failures:
Let's assume there are four micro-services: A calls B, B calls C, C calls D, and D is backed by a database.
And let's say there is some issue in the database. Due to this, D's requests fail and return a failure to C. C performs some default number of retries, but D still sends an error response since the database is down. The same happens at every stage—A, B, C, and D. Due to this reason, the database receives unnecessary calls and fails.
You might argue that we can have a combination of a rate limiter and a circuit breaker. Yes, we can, but doesn't it increase the complexity of the system unnecessarily?
However, a well-architected microservices system can minimize cascading failures using timeouts, retries, and circuit breakers effectively.
If there are some gray failures, where no system is down but, due to some TCP-level network issues, calls do not reach the service,
You might argue, this is why we have automated retries configured. But doesn't the latency get increased unnecessarily? The user needs to wait until the retries succeed.
Why micro-services at the start of any project?
Let's assume you are starting a new project and have created a micro-service architecture where you have 4 services.
Keeping in mind that there are only four up-streams/users to your service with very minimal requests per second, you are still maintaining 4 services with every resource (subnets/load balancers/ECS services, etc.). The ROI (Return on Investment) is very low. You would be paying more for infrastructure than you are earning from up-streams.
Hence, it is good to start with a monolithic architecture and later expand to micro-services if required.
Code duplicity:
Let's assume you have a database and all of your 4 micro-services want to communicate with the database. You need to have the code duplicated for the accessing part.
Yes, you can solve this by having a common repository. But still, it is an additional effort.
However, in a well-designed microservices architecture, teams use shared libraries, API gateways, or database access services to avoid code duplication.
But as everybody knows the disadvantages of a monolithic architecture, do we have any middle ground?
And yes, we have one where we combine a few advantages of both monolithic and micro-service architectures. It is called Modular Monolith.
Modular Monolith:
As the name itself reflects, it is a monolith but designed to be modular.
Earlier, if you had thought to create 4 micro-services, just create 4 modules here. (The spaghetti problem is resolved here.) You can reuse the code if needed. Communication between modules is just a method call. The maintenance of the codebase is low, and dependency management is affordable. No cascading failures and no unnecessary wastage of resources for retries. Less cost, since there are no unnecessary resources (single VPC, single load balancer, single ECS, etc.).
When should you not choose a modular monolith and instead go for micro-services?
If you want independent scaling solutions.
You don't want a single deployment unit.
If you don't want to maintain the discipline of modularity.
If you want different tech stacks for different modules.
If your team is very large, to reduce merge conflicts.
If there are any regulatory or compliance needs.
In conclusion, microservices are not inherently bad—they work well when scaling needs are high. But for many applications, a modular monolith is a strong middle ground, offering both maintainability and scalability without unnecessary complexity. Choosing the right architecture should depend on your business needs, team size, and scaling requirements.
References:
Follow for more insightful content.




Insightful