Strangler-fig migrations without losing a transaction

Legacy modernization fails for a small set of reasons that repeat across every industry we work in: dealerships, insurance, healthcare, government. The rewrite gets scoped as a single launch; the cutover is treated as the goal rather than the riskiest moment of the project; data validation happens after the lift-and-shift instead of being the gating event of each phase. The result is the famous failed migration — months of work, a panicked rollback, and the legacy system entrenched harder than before.

We have moved VB.NET, COBOL, classic ASP, and PHP3 platforms onto modern stacks without losing a transaction. The playbook we run is structurally identical across all of them.

Step 1 — audit, honestly

The first three weeks of every modernization engagement is paid audit time. We map the legacy system’s actual behaviour, not its documented behaviour: which endpoints are still in use, which database tables are still being written to, which scheduled jobs still fire, which business logic lives in stored procedures versus application code. The deliverable is an honest map of the system as it exists, not as the original team imagined it.

Step 2 — write the strangler-fig adapter

The strangler-fig pattern, named for the tropical vine that envelops a host tree, lets us deploy the new system behind the same external interface as the legacy one. Traffic is routed through an adapter that can send each request to either the legacy implementation or the new one. The two systems run in parallel; the adapter decides which one answers.

Critically, the adapter can also dark-launch — it routes the request to the legacy system as the source of truth while also calling the new system and comparing the responses. Mismatches get logged, not raised. The new system gets exercised under real production traffic before it is trusted to answer any real user.

Step 3 — contract tests pin the invariants

For every behaviour we are migrating, we write a contract test that the new system must satisfy. These tests are the formal definition of what the rewrite is allowed to change and what it is not. Cutover for any given behaviour is gated on the contract test passing in CI. Nothing flips by subjective judgement.

Step 4 — change-data-capture for the database

The legacy database is treated as the source of truth right up to the moment it is not. We stream every change off the legacy database via CDC — Debezium on PostgreSQL or Kafka Connect on SQL Server, depending on the source — into the new database. The new database is continuously updated by the legacy system until cutover. The cutover itself is then a matter of changing one configuration: which database is canonical.

Step 5 — phased cutover

We do not flip the whole system at once. We migrate one bounded context at a time: customer accounts, then orders, then billing, then reporting. Each phase has a written exit doc — what was migrated, what was validated, what the rollback path is. The legacy system continues to run alongside until every phase has been live for at least 30 days.

Step 6 — decommission with an audit trail

The legacy system is not turned off in a single thunderous moment. We freeze writes first, run a final reconciliation against the new system, archive the legacy database, and document the decommission with the auditor in mind. The legacy stack stays available read-only for at least 90 days after the final cutover.

Why this matters for the buyer

A strangler-fig migration is more expensive in calendar time than a lift-and-shift, but it is dramatically less expensive in risk. We have never had to roll back a cutover. We have never lost a transaction. The clients we have moved off mainframes still hold the audit reports we produced during decommission, because they’re the only documentation of what the legacy system actually did. That paperwork outlives the engagement.