Monolith to Microservices Migration
Architecture Diagram
When to Migrate
Not every monolith needs to become microservices. Consider migrating when you see these signals: deployment frequency is bottlenecked by coordination between teams, scaling requires scaling the entire application when only one component actually needs it, different parts of the system have fundamentally different reliability or performance requirements, or team autonomy is impossible because everyone works in the same codebase.
Do not migrate because microservices are trendy. A well-structured monolith deployed by a team of 15 engineers is often simpler, cheaper, and more reliable than a distributed system.
The Strangler Fig Pattern
Named after the strangler fig tree that grows around a host tree and eventually replaces it:
- Place a proxy (API gateway or reverse proxy) in front of the monolith. All traffic flows through this proxy.
- Identify a bounded context to extract. Pick one that is relatively self-contained, has clear data ownership, and changes frequently.
- Build the new service alongside the monolith. The service owns its own database from day one.
- Migrate data from the monolith's database to the new service's database. Use dual-write during the transition, then switch reads, then stop writes to the old table.
- Route traffic at the proxy layer from the monolith to the new service. Use feature flags for gradual rollout.
- Delete the old code from the monolith once the new service is stable (typically after 2-4 weeks of production traffic).
- Repeat for the next bounded context.
Data Migration Strategy
Data migration is where most teams get stuck. The key principles:
- Each service owns its data. No shared databases, period. If two services need the same data, one owns it and exposes it through an API or events.
- Use Change Data Capture (CDC) to stream changes from the monolith's database to the new service during the transition period. Tools like Debezium make this practical.
- Accept eventual consistency. In a distributed system, some data will be stale. Design your services to handle this (idempotent operations, compensation transactions).
- Foreign keys across services do not exist. Replace them with service-level references: store the ID and call the owning service when you need the data.
The Shared Library Problem
Monoliths often have "common" libraries that contain business logic, data models, and utility functions. When you extract services, each service pulls in this shared library, creating hidden coupling. The fix: split the shared library into two buckets. Pure utilities (string formatting, date parsing) can stay shared. Anything containing business logic or data models must be duplicated into each service. Accept the duplication. It is the price of independence.
Migration Timeline
Realistic timelines based on industry experience:
| Monolith Size | Team Size | First Service | Full Migration |
|---|---|---|---|
| < 100K LOC | 10-20 | 2-3 months | 6-12 months |
| 100K-500K LOC | 20-50 | 3-6 months | 12-24 months |
| 500K+ LOC | 50-200 | 6-12 months | 2-5 years |
These timelines assume you already have CI/CD, containerization, and basic observability in place. If you do not, add 3-6 months for platform readiness.
Key Points
- •The Strangler Fig pattern is the safest migration strategy. Wrap the monolith with a proxy and gradually route traffic to new services
- •Extract services along domain boundaries, not technical layers. A 'User Service' is better than a 'Database Service'
- •Data is the hardest part. Shared databases between the monolith and services create coupling that defeats the purpose of decomposition
- •You need robust observability before you start splitting. You cannot debug distributed systems with monolith-era logging
- •Most organizations that fail at microservices migration fail because they decompose too aggressively too early. Start with 2-3 services, not 20
Common Mistakes
- ✗Big bang rewrite, where you attempt to rewrite the entire monolith at once instead of incrementally migrating bounded contexts
- ✗Distributed monolith, where extracted services still share a database, deploy together, and cannot function independently
- ✗Ignoring the shared library trap. Common libraries that embed business logic create hidden coupling between services
- ✗Not establishing service contracts before splitting. Changing APIs after services are deployed is exponentially harder
- ✗Underestimating the operational cost. Each new service needs its own CI/CD pipeline, monitoring, alerting, and on-call rotation