Monolithic Vs Microservices Architecture

In this article, I will explain the difference between monolithic and microservices architecture. After that, I will suggest some approaches for transitioning from monolithic to microservices.
The difference
Practical Example of Monolithic Architecture:
Imagine an e-commerce platform where you have components for user management, product catalog, cart, checkout, and order processing. In a monolithic setup:
All components (like catalog, user management, cart, etc.) are part of one codebase.
They share the same database and may even share the same code files.
When developers make changes to one feature, they need to redeploy the entire application.
Scaling is uniform; if you want more capacity for the product catalog but not for other features, you’re forced to scale the entire application.
Practical Example of Microservices Architecture:
Taking the same e-commerce platform example but with a microservices approach:
- User Management, Product Catalog, Cart, Checkout, and Order Processing are each separate microservices.
User Management Service might be developed in Java.
Product Catalog Service could be developed in Node.js.
Checkout Service might use Python.
Each service has its own repository, database, and deployment pipeline.
Services communicate with each other over REST APIs, gRPC, or a message broker like RabbitMQ or Kafka.
If there’s a high demand for the catalog service, only that service can be scaled up independently.
In general, small or simple applications often start with a monolithic approach and, as they grow, might transition to microservices.
The transition
Approach 1 : Strangler Fig Pattern
This pattern is inspired by the way a strangler fig tree grows around its host tree, slowly taking over until the host tree is completely enveloped. Similarly, in this pattern, new functionality is built as separate component or service, eventually “strangling” and replacing the old monolithic system.
How the Strangler Fig Pattern Works
Identify Functionality to Replace: You identify parts of the monolithic application that need to be refactored or replaced. Typically, you’d start with less critical or independent modules.
Build New Functionality as Microservices: Implement the selected functionality in a new microservice, with a separate codebase, tech stack, and independent deployment capabilities.
Redirect Traffic to New Microservices: Use techniques like API gateways or proxies to route requests meant for the old monolithic module to the new microservice. This can be done gradually, testing and fine-tuning as you go.
Incremental Migration: Continue building new features and migrate more parts of the monolithic application to microservices until the monolithic app is either entirely replaced or minimally involved.
Sunset the Monolith: Once all critical features are moved to microservices, the monolithic app can be decommissioned.
Real-World Example of the Strangler Fig Pattern
Netflix used the Strangler Fig Pattern during its migration to microservices. Initially, Netflix operated as a monolithic application but faced scaling challenges as it grew globally. By applying the Strangler Fig Pattern, they were able to move each service (recommendations, streaming, user profiles, etc.) to microservices, allowing each component to scale independently and supporting high availability for millions of users.
Question*: When we identify a functionality to replace and build it as a microservices using strangler fig pattern, does that functionality still remain in monolith?*
Answer*: the functionality initially remains in the monolith during the migration phase. However, once the new functionality is built as a microservice and verified to work correctly, the corresponding functionality in the monolith is typically phased out or disabled.*
Approach 2 : Branch by Abstraction
Steps in Branch by Abstraction
Introduce an Abstraction Layer: Build an abstraction layer to allow continued communication between the service that is being replaced and the entities requesting that service. This abstraction layer defines the behavior of the service being modified.
Refactor Existing Code to Use the Abstraction: Implement the existing functionality behind this new abstraction. Replace direct calls to the old implementation with calls to the abstraction. Ensure all parts of the codebase are interacting with the abstraction, not the old implementation directly.
Implement the New Functionality Behind the Abstraction: Build the replacement system, linking each rebuilt feature into the abstraction layer as they get finished.
Switch to the New Implementation: Once the new implementation is complete and tested, switch the abstraction to use the new implementation. This can be done via configuration or a feature toggle.
Remove the Old Implementation: After the new implementation is fully operational and verified, deprecate and remove the old implementation and dismantle the abstraction layer.
Real-World Example: Migrating Logging Frameworks
Imagine you’re migrating a large application from one logging library (e.g., Log4j
) to another (e.g., SLF4J
):
Introduce a logging abstraction, such as
Logger
.Create implementations for both
Log4j
andSLF4J
behind the abstraction.Refactor existing code to use the
Logger
abstraction.Gradually implement and test
SLF4J
integration.Switch the abstraction to use
SLF4J
and removeLog4j
once fully migrated.
Thank you for your time! 😊
Connect with me on LinkedIn
Subscribe to my newsletter
Read articles from Shubham Agrawal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
