Modernizing a legacy platform without a big-bang rewrite
The rewrite that replaces everything at once is the one most likely to fail. Here's the patient alternative that actually ships.
Every legacy platform comes with a fantasy attached: one clean rewrite, a modern stack, and all the accumulated mess left behind for good. It's the most expensive fantasy in software. The teams that chase it tend to spend a year or two building a replacement in parallel, discover the old system did far more than anyone documented, and either ship something that quietly does less — or never ship at all.
We've modernized enough systems, our own and clients', to be suspicious of the clean-slate instinct. The good news is there's a patient alternative that gets you to the same modern place with a fraction of the risk. It's less dramatic than a grand rewrite, and that's exactly why it works.
The big-bang rewrite is a bet against yourself
A full rewrite asks you to reproduce years of hard-won behaviour — every edge case, every quiet business rule, every "we handle it this weird way because a customer needed it in 2019" — before you're allowed to ship anything at all. For the entire length of that project you're carrying two systems and delivering value from neither. The old one gets no investment because it's doomed; the new one isn't ready. Momentum, the thing that actually keeps a project alive, has nowhere to accumulate.
Worse, the requirements you're rebuilding against are mostly undocumented and live only in the running code. A big-bang rewrite is really a bet that you can perfectly re-derive all of that behaviour from scratch, in one shot, while the business keeps moving underneath you. That bet usually loses, and the failures are famous for a reason.
The alternative isn't "don't modernize." It's "never stop shipping while you do."
Wrap the old system before you replace it
The pattern that works has an unglamorous name — the strangler fig, after the vine that grows around a tree until it can stand on its own and the original quietly disappears. In software terms: put a seam in front of the legacy system, route through it, and replace what sits behind that seam one piece at a time. Users keep hitting the same front door. What's behind it changes gradually, and nobody has to hold their breath for a single terrifying cutover weekend.
This is close to the posture we took building the recreation platform for Sport Calgary. The city's recreation data lived across a patchwork of existing systems and sources that were never designed to be read together — and the right move was not to rip them out. It was to build a clean layer in front of them: one interface that presents 1,286 facilities as a single coherent map while absorbing the mess of the sources behind it. That layer — an anti-corruption layer, in the jargon — is what lets the modern experience stay clean even when its inputs aren't. You can improve or swap any upstream source later without the resident ever noticing.
The discipline is to define the seam first and then defend it. Everything new lives on the modern side of that boundary; the legacy system becomes something you talk to through a narrow, well-understood contract rather than something your new code is tangled into.
A rewrite isn't done when the new code works. It's done when the old code is gone — and most big-bang rewrites never actually get there.
Migrate data like it's the actual product
Here's the part teams underestimate almost every time: the hard part of modernization is rarely the code. It's the data. Years of records, in schemas that made sense at the time, full of nulls and workarounds and one-off corrections nobody remembers making. The new system is only as trustworthy as the data you bring across, and users forgive a plain interface far faster than they forgive a wrong number.
So we treat migration as a first-class piece of engineering, not a scramble at the end. In practice that means a few non-negotiables:
- Dual-write before you cut over. For a while, new writes land in both the old and new systems, so the modern store is populated and provably correct under real traffic long before it's load-bearing.
- Reconcile continuously. Run both systems side by side and compare their answers. Every disagreement is either a migration bug or an undocumented rule you just discovered — both worth finding before customers do.
- Backfill idempotently. The historical import has to be safe to run again and again, because you will run it again and again as you learn what the first pass missed.
- Keep a rollback that actually works. Until the old system is truly gone, every step forward needs a step back you've rehearsed — not one you're hoping exists.
None of this is glamorous. All of it is the difference between a migration that lands quietly and one that becomes an incident with a customer's name on it.
Move in slices thin enough to reverse
Once the seam is in place and data flows both ways, modernization becomes a sequence of small, boring, reversible moves instead of one heroic leap. Pick a single capability — one report, one workflow, one screen. Build it on the modern side. Route a sliver of traffic to it behind a flag. Watch it against the old path. When it's genuinely better, widen the flow; when it isn't, turn it off and nobody outside the team ever knew.
That rhythm — slice, shadow, compare, cut over, delete the old code — is the whole method. Each pass is small enough to reason about, small enough to reverse, and small enough to ship this week instead of next year. The legacy surface area shrinks steadily, and every increment leaves the product in a working, releasable state. You're never more than a few days from a safe place to stop.
The delete step matters as much as the build step. Modernization that only ever adds is just a bigger system with a nicer front end bolted on. The real win comes when old code and old infrastructure actually go away — that's the vine finishing its job.
Know which parts deserve to survive
Not everything old is legacy in the pejorative sense. Some of that battle-tested code encodes exactly the edge cases a rewrite would spend a year rediscovering. Part of doing this well is being honest about what's genuinely holding you back — the brittle, the unscalable, the unsafe — versus what's simply old and working fine. The goal is a modern platform, not novelty for its own sake.
That judgement is where a modernization either succeeds or wastes its budget. Replace the parts that block you, wrap the parts that don't, and preserve the hard-won behaviour worth keeping. The best modernizations we've done look, from the outside, almost anticlimactic: the product kept working the whole way through, the risky weekend never happened, and one day the last piece of the old system was switched off without ceremony.
That's the bar. Not a dramatic relaunch, but a platform that became modern so steadily the people relying on it barely felt the ground move.
If you're staring at a system that's too important to break and too old to keep living with, we'd love to help you find the seams — and a path that keeps shipping the whole way through.
Written by the Appè Latte studio.