A monolith can become difficult to change long before it becomes impossible to run.
Deployments start affecting unrelated areas. A change in refunds requires regression testing checkout. One database becomes the integration layer between half the system. Teams wait for each other. Releases grow larger, rollback becomes scarier, and nobody is completely sure where one business capability ends and another begins.
At that point, somebody usually draws this:
The destination looks simple.
The migration is not.
Moving from a monolithic architecture to microservices is not primarily a matter of creating smaller applications. It is a controlled transfer of responsibility across boundaries while the existing system continues serving production traffic.
That makes it both an architectural problem and a project-management problem.
A useful migration therefore needs more than a target architecture.
It needs a path.
Start with the reason, not microservices
Before decomposing anything, ask a more uncomfortable question:
What problem are we actually trying to solve?
Microservices introduce their own costs: network failures, distributed tracing, eventual consistency, deployment coordination, infrastructure overhead, data ownership problems, versioned contracts, retries, duplicate messages, and substantially more operational surface area.
If the real problem is simply poor module boundaries inside the codebase, a modular monolith may be the better answer.
A migration should therefore start with measurable architectural drivers:
- independent deployment of high-change capabilities;
- different scaling characteristics;
- clearer team ownership;
- fault isolation;
- regulatory or security boundaries;
- independent technology evolution;
- reducing coupling around specific business capabilities.
This is where the previous discussion about Spikes and ADRs becomes useful.
A Spike can investigate whether extracting a capability actually improves the characteristics we care about.
An ADR can record why that boundary was selected and which consequences were knowingly accepted.
The ADR tells us why.
The migration plan tells us how and when.
Those are different artifacts.
Do not migrate the monolith. Migrate a capability.
Imagine a payment platform containing:
A dangerous project starts with:
We are migrating the monolith to microservices.
That scope is enormous and gives the programme very few meaningful intermediate success states.
A better question is:
Which capability should we extract first?
Suppose Refunds is selected because it has clear business semantics, changes frequently, integrates with unreliable external providers, and has different scaling and failure characteristics from checkout.
Now the project becomes much more concrete:
That is something architecture and delivery teams can actually manage.
The target architecture is not the migration architecture
This distinction is easy to miss.
The desired end state might be:
But production cannot teleport from the current state to that diagram.
For some period of time, the real architecture will look more like this:
That intermediate state may live for weeks or months.
It is not temporary clutter to ignore.
It is the production architecture during the migration.
And it needs to be designed.
The Strangler Fig pattern
One of the best-known approaches to incremental replacement is Martin Fowler’s Strangler Fig Application pattern.
Instead of rewriting the entire system and switching everything at once, new functionality gradually grows around the old system.
Conceptually:
But the percentages are less important than the principle:
Replace capabilities incrementally while keeping the system operational.
This creates something extremely valuable in architecture programmes:
reversibility.
If the first extraction performs badly, creates unexpected coupling, or proves operationally expensive, the organisation has learned something without betting the entire platform.
That is architecture behaving like risk management.
Migration needs phases
Large architectural initiatives become much easier to reason about when separated into explicit phases.
A practical structure might look like this:
Phase 1 — Discovery
Understand why the capability is being extracted.
Map dependencies.
Identify database tables, APIs, scheduled jobs, events, operational dashboards, external integrations, and other modules that depend on it.
Run Spikes where uncertainty is material.
Produce ADRs for significant decisions.
The output is not yet a microservice.
It is reduced uncertainty.
Phase 2 — Establish the boundary
Define what the new service owns.
For Refunds, that might mean:
Refund Service owns:
- refund lifecycle
- refund state machine
- idempotency
- provider interaction
- retry policy
- refund persistence
- refund events
The boundary matters more than the framework.
A Spring Boot service with the wrong business boundary is still the wrong architecture.
Phase 3 — Build alongside the monolith
Do not immediately remove the existing path.
Create the new service while production continues through the monolith.
This gives the team somewhere to migrate traffic gradually.
Phase 4 — Validate in parallel
Before trusting the new path with all production traffic, compare behaviour.
Depending on the capability, techniques can include:
The second path does not necessarily execute external side effects. For payments and refunds, blindly duplicating a production command would obviously be dangerous.
But calculations, transformations, routing decisions, or state transitions can often be compared safely.
This turns migration from:
We think the new service works.
into:
Across 1.8 million representative operations, the old and new implementations agreed in 99.99% of the cases, and we understand the remaining differences.
That is a much stronger basis for moving traffic.
Move traffic, not hope
Eventually the new service must become authoritative.
Do not think only in terms of deployment.
Think in terms of traffic migration.
A migration might progress through controlled stages:
Stage 1 100% old 0% new
Stage 2 95% old 5% new
Stage 3 75% old 25% new
Stage 4 25% old 75% new
Stage 5 0% old 100% new
The exact percentages are implementation details.
The important part is that each stage has entry criteria, observations, and rollback conditions.
For example:
Proceed if:
error rate < threshold
p95 latency < threshold
duplicate rate = expected
reconciliation = clean
DLQ growth = normal
provider errors = understood
Architecture has now become measurable.
Data is usually the hard part
Extracting Java or TypeScript code into another repository is rarely the hardest part.
Data ownership is.
The monolith may currently do this:
Creating four services that all continue directly accessing those same tables gives us:
We have distributed the applications while preserving much of the original coupling.
This is sometimes a valid transitional state.
It is a poor place to stop accidentally.
A stronger destination is explicit ownership:
Now another problem appears.
A database transaction can no longer quietly coordinate everything.
Distributed boundaries force previously hidden architectural questions into the open:
- What is the source of truth?
- Which service owns the state transition?
- What happens when a downstream call fails?
- Do we need eventual consistency?
- What is retried?
- What must be idempotent?
- What happens if an event is delivered twice?
- How do we reconcile inconsistent states?
Microservices do not eliminate complexity.
They move complexity across boundaries.
Project management has to follow the architecture
Traditional project plans often describe migration in technical tasks:
Create repository
Create service
Create database
Implement endpoints
Deploy service
All of those tasks can be completed while the migration itself remains nowhere near finished.
A more meaningful plan tracks capability ownership.
For example:
Refund Extraction
[✓] Boundary agreed
[✓] ADRs accepted
[✓] Service operational
[✓] Observability available
[✓] Data ownership established
[✓] Shadow validation passed
[✓] 5% production traffic
[✓] 25% production traffic
[ ] 100% production traffic
[ ] Old refund path disabled
[ ] Old database access removed
[ ] Dead code removed
This changes what “done” means.
The service existing is not the milestone.
The responsibility having moved is.
Every migration step needs an exit condition
One of the most useful disciplines in architecture programmes is defining what allows the team to move to the next phase.
Without exit criteria, migrations tend to move according to calendar pressure:
We planned to switch on Thursday.
With exit criteria, the decision becomes evidence-based:
Error rate, latency and reconciliation are inside the agreed thresholds, so we can increase traffic.
That is a very different engineering culture.
Observability is part of the migration architecture
A migration without observability is extremely difficult to control.
Before moving production traffic, the new boundary should expose enough information to answer:
Is it working?
Is it slower?
Is it failing differently?
Are retries increasing?
Are duplicate requests appearing?
Where is time being spent?
Can we correlate the request across systems?
That means metrics, logs, and distributed traces are not something to “add later.”
They are part of the migration mechanism.
During the transition, a trace may cross both worlds:
Without correlation across that boundary, operational complexity increases precisely when the architecture is already at its most complicated.
Decommissioning is part of the project
Migration projects have a dangerous moment:
New service works.
Production uses it.
Everyone celebrates.
But the old implementation remains.
Six months later:
Refund Service
+
Old Refund Module
+
Old Tables
+
Old Jobs
+
Compatibility Code
+
Temporary Routing
The organisation now owns more architecture than before the migration.
Decommissioning therefore belongs in the original plan.
Deleting the old architecture is not cleanup.
It is the final migration phase.
Architecture and project management meet in the middle
A system architect can draw an excellent target:
A project manager can build an excellent roadmap:
Q1 → Q2 → Q3 → Q4
Neither is sufficient alone.
The useful artifact connects them:
This is where architecture stops being a picture of the future and becomes an executable strategy.
The migration itself is architecture
There are really three architectures involved:
CURRENT STATE
│
▼
TRANSITION ARCHITECTURE
│
▼
TARGET STATE
Teams naturally spend most of their design effort on the first and third.
The middle deserves just as much attention.
During migration, the organisation may temporarily operate duplicate paths, compatibility APIs, routing rules, event bridges, shared databases, shadow traffic, feature flags, and synchronization mechanisms.
Those components may never appear in the final diagram.
They still need architecture decisions, ownership, observability, security, testing, and explicit retirement plans.
That is why a large architectural migration is not simply a refactoring exercise.
It is the controlled evolution of a running system.
A useful mental model
The entire approach can be reduced to:
Or even more compactly:
Architecture defines the destination. Migration architecture defines the safe path. Project management makes that path executable.
A monolith does not become a healthy microservice architecture because enough services were created.
The migration succeeds when responsibilities, data ownership, traffic, operational ownership, and failure handling have moved deliberately — and the old architecture can finally be deleted.
That is a much more meaningful definition of done.
References
- Fowler, M. — Strangler Fig Application. MartinFowler.com.
- Newman, S. — Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith. O’Reilly Media.
- Newman, S. — Building Microservices, 2nd Edition. O’Reilly Media.
- Ford, N., Richards, M., Sadalage, P. & Dehghani, Z. — Software Architecture: The Hard Parts. O’Reilly Media.
- Richards, M. & Ford, N. — Fundamentals of Software Architecture: An Engineering Approach. O’Reilly Media.
- Nygard, M. — “Documenting Architecture Decisions.”
- Fowler, M. — Feature Toggles (aka Feature Flags).
- Corebaseit — Spike First, ADR Second: Evidence and Rationale in System Design.



















