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:

  1. What was the original reason to extract this module?
  2. Has that reason materialized? Is it still the right problem to solve?
  3. What is the current cost of leaving it in the monolith?
  4. 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.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why Your Docker Image Works Locally But Breaks in Production

Local Docker and production environments differ in architecture, user permissions, resource limits, networking, and secret injection. Most "it works on my machine" container failures trace back to a small set of fixable mismatches.

Read more

Spring Boot Security Checklist — What to Verify Before Going to Production

Security gaps in Spring Boot applications follow predictable patterns. This checklist covers authentication, authorization, data protection, infrastructure configuration, and the vulnerabilities that survive code review. Work through it before every production deployment.

Read more

Logs Are Useless If Nobody Reads Them

Most applications produce logs. Few produce logs that are useful during an incident. The gap between logging and observable behavior is where debugging goes to die.

Read more

Aggregations Are Not Just COUNT and SUM. There Is More.

Most developers use a fraction of the aggregation functions available in modern SQL databases — statistical functions, ordered-set aggregates, hypothetical aggregates, and string aggregation can replace entire post-processing pipelines that currently live in application code.

Read more