Software Architecture
Modernising a legacy system without stopping the business
Rewriting a critical system from scratch is one of the riskiest projects a business can take on. Incremental replacement is slower to describe and far safer to deliver.
Most established businesses have at least one system that everyone depends on and nobody wants to touch. It might be a desktop application built fifteen years ago, a heavily customised database, or a web app on a framework that stopped receiving security updates. It works, mostly. But changes are slow, the people who understood it have moved on, and every release feels like a gamble.
The instinct is to replace it with something new in one go. We understand the appeal, and we also advise against it in almost every case. The safer route is to modernise incrementally, keeping the business running on the old system while the new one takes over piece by piece.
Why big-bang rewrites go wrong
A legacy system is not just old code. It is years of accumulated business rules, many of them undocumented, some of them handling edge cases that nobody remembers until they break. A rewrite from scratch has to rediscover all of that, usually by getting it wrong in production.
Rewrites also create a long period where the business gets no new value. The old system is frozen, or worse, still changing while the new one tries to catch up. The cutover becomes a single high-stakes event, and if it goes badly there is often no easy way back. The longer the rewrite runs, the more pressure there is to launch before it is truly ready.
A legacy system is not just old code. It is years of business rules that nobody wrote down.
The strangler fig approach
The alternative takes its name from a plant that grows around a host tree until it can stand on its own. In software terms, you place a layer in front of the old system, often a routing proxy or an API gateway, and gradually redirect specific functions to new components. Each piece is built, tested and switched over individually. The old system shrinks until it can be switched off.
Choosing what to move first is a judgement call. Good candidates are areas that change often, cause the most pain, or have clear boundaries with the rest of the system. A common example is a distribution business whose order entry lives in an old desktop application: the first step might be a new web-based order screen that writes into the existing database, giving staff an immediate improvement while the back end is untouched. Later steps move pricing, then stock allocation, then reporting.
This style of work depends heavily on good API integrations, because for a long period the old and new systems have to cooperate. Clean interfaces between them are what make each step reversible.
Capture current behaviour before changing it
Before you replace a piece of a legacy system, you need to know exactly what it does, including the behaviour that looks like a bug but that the business now relies on. Characterisation tests are the tool for this. Rather than testing what the code should do, they record what it actually does today for a wide range of inputs.
In practice that might mean running a year's worth of real orders through the old pricing logic and storing the results, then checking that the new implementation produces the same output. Where the results differ, someone makes a conscious decision about which is correct. This is unglamorous work, and it is the single biggest factor in whether a migration goes smoothly.
Data migration is its own project
Data outlives code. Legacy databases typically contain duplicate records, inconsistent formats, fields repurposed for things they were never designed for, and references that point nowhere. Moving that data is rarely a simple export and import. A sound approach usually involves:
- Profiling the existing data to understand its real shape and quality, not the shape the schema suggests.
- Agreeing cleaning and mapping rules with the people who use the data, and writing them as repeatable scripts.
- Rehearsing the migration several times against copies of production data, measuring how long it takes.
- Reconciling results with counts, totals and spot checks that business users sign off.
- Planning the cutover window and a tested rollback route before it happens.
Where systems must run side by side for a period, you will also need a strategy for keeping data in sync, whether that is one system acting as the source of truth or changes flowing in both directions. Two-way sync is considerably harder; avoid it if you can.
Run in parallel and switch gradually
For critical functions, running old and new side by side and comparing outputs before switching over is worth the extra effort. Feature flags let you route a small subset of users or transactions to the new path first, watch closely, and widen gradually. If something goes wrong, you switch back in minutes rather than days.
This is also the moment to put modern foundations in place: automated deployments, monitoring, backups you have actually restored from, and infrastructure defined as code. Good cloud and DevOps practice is what stops the new system becoming the next legacy system.
When not to modernise
Not every old system needs replacing. If it is stable, secure, rarely needs changing and supports the business well, the best decision may be to leave it alone and invest elsewhere. Sometimes the right move is to wrap it with an API so newer tools can use its data, and stop there. And if a mature product now does what the legacy system does, migrating to that product may be cheaper than building anything, a question we cover in custom software versus off the shelf.
Final thoughts
Modernisation works best as a series of small, reversible steps, each delivering something useful. It asks for patience and discipline, but it keeps the business running throughout and turns an intimidating project into a manageable one. If you are weighing up what to do with an ageing system, a short assessment through our software development team can help you decide where to start, or whether to start at all.