You Don't Have to Migrate Everything at Once
by Arif Ikhsanudin, Backend Developer
The sunk cost trap in migration projects
Twelve months into your microservices migration, you've extracted four of twenty planned services. Three of them are running well. One is a mess you're maintaining alongside the equivalent monolith code because the cutover never happened. Eight more services are "in progress" — meaning someone started an extraction six months ago, the branch diverged, and now merging back is a project unto itself. The remaining eight haven't been started.
Your engineers are spending 40% of their time on migration work. The monolith is still taking 60% of new features. You committed to "finish the migration" as an org-level goal, which means stopping is politically difficult even though stopping might be the right engineering answer.
This is the destination when "migrate everything" is the plan rather than "migrate what makes sense and evaluate." The premise that you need to extract every module into a service to benefit from microservices is wrong. The benefit comes from extracting the modules where microservices architecture provides specific, measurable value. For the rest, the monolith may be perfectly adequate.
Extracting by value, not by plan
Start each potential extraction with: what specific problem does extracting this module solve? If the answer is "our migration plan says to," that's not a good enough reason. If the answer is "the Search team wants to experiment with Elasticsearch and the current monolith's database-backed search is a bottleneck for their iteration velocity," that's a real reason.
Value-based extraction questions:
- Does this module have scaling requirements significantly different from the rest of the monolith?
- Does a specific team need deployment independence from this module to move faster?
- Does this module have a compliance or security requirement that demands data isolation?
- Is this module in a different language or runtime than the monolith, making it awkward to maintain in-process?
If none of those apply, the module might be better left in the monolith, possibly with improved internal boundaries. A well-bounded module inside a monolith is often easier to maintain than a separate service with all its operational overhead.
The hybrid architecture as a valid steady state
It is entirely acceptable — and often optimal — to run a hybrid architecture indefinitely: a monolith handling the majority of business logic, alongside a small number of services for components that genuinely benefit from independence.
A monolith plus a search service. A monolith plus an ML inference service. A monolith plus a real-time notification service. Each extracted for a specific, justified reason. The monolith is not a legacy thing to be eliminated — it's the appropriate deployment unit for the parts of the system where the monolith's simplicity outweighs the services' benefits.
The hybrid architecture requires one discipline: the monolith must not directly call into service code (it communicates via APIs or events), and services must not directly access the monolith's database. These boundaries make the hybrid sustainable. Without them, you get a distributed monolith — the worst of both worlds.
Managing the extraction backlog
If you're mid-migration with many planned extractions not yet started, evaluate each one against current criteria rather than original plan criteria:
For each un-started extraction:
- What was the original reason to extract this module?
- Has that reason materialized? Is it still the right problem to solve?
- What is the current cost of leaving it in the monolith?
- What would the extraction cost in engineering time, and what's the ROI?
Some modules that seemed like extraction candidates twelve months ago may no longer be worth extracting. New feature development has changed the system. Team structure has changed. Original pain points have been resolved other ways. Abandoning planned extractions that no longer make sense is not failure — it's updated decision-making with current information.
Stopping criteria
How do you know when you've done enough? When:
- The teams that were blocked by the monolith are unblocked (they have deployment independence for their domains)
- The scaling bottlenecks that required separation are resolved
- The compliance requirements that demanded isolation are satisfied
Not when every module is a service. That endpoint is arbitrary and likely to produce overengineered infrastructure for modest benefit. The architecture is not a destination — it's a response to specific engineering and organizational requirements. When those requirements are met, stop extracting.
What you should preserve as you extract: the ability to move quickly within the monolith for new features that don't need to be services. A large, untangled, well-tested monolith core alongside a few well-chosen services is a legitimate long-term architecture for many organizations. Resist the pressure to complete a migration for its own sake.