Migrating a monolith to microservices is one of the most common architecture projects I'm asked to lead. It's also one of the most frequently botched. Here's the approach that actually works, based on having done this several times.
First: Are Microservices the Right Answer?
Before anything else, ask whether microservices actually solve your problem. The usual motivations:
- Independent scaling — different parts of your system have wildly different load profiles
- Independent deployment — you want teams to ship without coordinating releases
- Technology flexibility — you need different runtimes or databases for different domains
- Team autonomy — Conway's Law is real; your architecture should reflect your org structure
If none of these apply, a well-structured modular monolith is almost always the better choice. Microservices add real operational complexity — don't pay that cost for imaginary benefits.
The Strangler Fig Pattern
The worst migrations are big-bang rewrites. The best use the Strangler Fig pattern: incrementally extract functionality from the monolith while keeping it running, until the monolith is gone.
The steps:
- Deploy a proxy in front of the monolith that can route requests
- Extract a bounded context into a new service
- Route traffic for that domain to the new service
- Remove the code from the monolith
- Repeat
This approach keeps the system live throughout the migration, lets you validate each extracted service independently, and allows you to pause or reverse at any stage.
Domain-Driven Design as Your Map
The biggest risk in microservices is drawing service boundaries in the wrong place. Services that are too granular create chatty networks and distributed monoliths. Services that are too coarse defeat the purpose.
Domain-Driven Design gives you the vocabulary to draw the right boundaries. Specifically:
- Bounded contexts define where a model is valid — these map to service boundaries
- Aggregates define consistency boundaries — don't split them across services
- Domain events define the communication contract between contexts
Run an Event Storming workshop with your team before you draw a single service boundary. The insights you get are worth more than any architecture diagram.
Data Is the Hard Part
Network calls are easy. Data is hard.
Each service should own its data — no shared databases. This is non-negotiable for true independence, but it creates challenges:
Cross-service queries: You can't just JOIN across service boundaries. Options: API composition, CQRS with read models, or a dedicated reporting database fed by events.
Transactions: Distributed transactions (2PC) are a trap. Use the Saga pattern instead — a sequence of local transactions coordinated by events or an orchestrator.
Initial data split: When you extract a service, you need to copy its slice of the database. Use dual-writes (write to both old and new) during the transition period, then cut over.
Operational Readiness Checklist
Before going to production with your first extracted service, verify:
- [ ] Health check endpoint and liveness/readiness probes
- [ ] Structured logging with correlation IDs
- [ ] Distributed tracing instrumentation (OpenTelemetry)
- [ ] Circuit breakers on all outbound calls
- [ ] Graceful shutdown handling
- [ ] Resource limits and autoscaling policies
- [ ] Runbook for common failure scenarios
Teams often underestimate this list. Build the operational foundation with your first service and the rest get it for free.
Managing the In-Between State
During migration, you'll have a hybrid system — some features in the monolith, some in services. This is uncomfortable but inevitable. A few practices that help:
API versioning from day one: Don't assume you'll get the interface right the first time.
Feature flags: Ability to roll back traffic to the monolith at any point.
Canary deployments: Route a percentage of traffic to the new service before full cutover.
The Migration Takes Longer Than You Think
Every migration I've been involved with has taken longer than the initial estimate. The data migrations are always more complex than expected. The edge cases discovered in the monolith's code are always surprising. Build in buffer and communicate realistic timelines to stakeholders.
The end state is worth it — but only if you get the fundamentals right.