Architecture reviews focus on whether the system works.
The real question is whether it can move.
You can build a system that handles production traffic perfectly, scales under load, recovers from failures gracefully — and falls apart the moment you try to migrate it to a new database, a new message broker, or a new cloud provider. That's not a migration problem. That's an architecture problem that migration exposed.
Abstractions you can't migrate through aren't abstractions
Every system has seams. The interfaces between modules, services, and infrastructure components. We call them abstractions and assume they'll hold when we need to swap the thing behind them.
Most of them don't.
The database access layer that was supposed to make the storage backend interchangeable turns out to leak query syntax. The message queue abstraction that was supposed to work across brokers doesn't handle Kafka's partition semantics differently from RabbitMQ's queue semantics. The "cloud-agnostic" deployment config has one provider's assumptions baked into every line.
These aren't lazy engineering. They're honest abstractions that worked fine until you asked them to do the one thing they were designed for. The abstraction was never tested by the abstraction. It was tested by the implementation behind it, and the implementation shaped the interface more than the interface shaped the implementation.
Migration is the moment that breaks. You pull the old thing out, put the new thing in, and discover that the seam wasn't a seam. It was a weld.
The strangler fig is a migration strategy, not a design pattern
The strangler fig pattern gets cited as a way to migrate incrementally — build the new system alongside the old, route traffic gradually, decommission the old when it's no longer needed. It's sound advice.
But it's a migration strategy, not an architecture. You can't strangler-fig your way out of a bad abstraction. If every call to the old system leaks its semantics into the calling code, the new system has to replicate those semantics exactly, or you have to change every caller. The strangler fig helps you manage the cutover. It doesn't make the cutover unnecessary.
The teams that migrate successfully aren't the ones with the best migration strategy. They're the ones whose architecture already had real seams. The migration is easy because the system was designed to allow it. The migration is hard because it wasn't, and no pattern fixes that after the fact.
This is the same shape as architecture being what happens after success. The architecture isn't what you designed. It's what the system does under pressure. Migration is pressure.
Zero-downtime migration is a data problem, not a deploy problem
Most teams approach migration as a deployment challenge: how do I switch traffic from old to new without downtime? Blue-green deploys, canary releases, feature flags. These are useful tools.
They're also the easy part.
The hard part is the data. If you're migrating from one database to another, you need to move the data, keep both systems in sync during the transition, and cut over without losing writes. That's not a deploy problem. It's a distributed systems problem with a consistency requirement.
The strategies are familiar to anyone who's done this work:
- Dual-write to both systems, with a reconciliation job to catch discrepancies.
- Change data capture from the old system, replay into the new.
- Backfill the new system from a snapshot, then catch up with CDC.
- Cut over reads first, then writes, or vice versa.
Each of these is a consistency protocol. Each has failure modes. Dual-write can diverge if one write fails. CDC has lag. Backfill plus catch-up has a race window. None of these are solved problems. They're engineering decisions with trade-offs, and the right one depends on your data, your traffic, and your tolerance for inconsistency.
This is where the most expensive bugs live. Not in the code. In the decision about which migration strategy to use, made under time pressure, with incomplete information about how the old system actually behaves.
The migration that reveals the architecture
Here's the uncomfortable part. Migration doesn't just test your architecture. It reveals it.
When you migrate, you discover what your system actually depends on. Not what the docs say. Not what the architecture diagram shows. What the code actually does, at runtime, under real traffic, when you change the thing it talks to.
You find the service that makes assumptions about message ordering that the new broker doesn't guarantee. You find the query that worked because the old database happened to serialize transactions in a specific way. You find the health check that was actually a liveness probe for the old infrastructure's specific failure mode.
These aren't bugs in the migration. They're the architecture. The system was always this shape. Migration just made it visible.
The teams that migrate cleanly aren't the ones with the best migration plan. They're the ones whose architecture was already honest about its dependencies. The seams were real. The abstractions held. The system was designed to move, even if it never had to before.
The one-line version
Your architecture isn't what works in production. It's what survives the move.