Haliviq
Technology9 min read·

Modernizing Legacy Systems Without a Big-Bang Rewrite

A legacy system that still earns money is usually the one nobody dares to touch. The first idea many teams reach for is a full rewrite, but those projects tend to run long, and the old system still needs changes in the meantime. This article describes a lower-risk path: replace it piece by piece while it keeps working.

H

Haliviq Team

Digital product studio, Bangkok

Modernizing Legacy Systems Without a Big-Bang Rewrite

Why a big-bang rewrite is risky

Old systems hold more business rules than anyone remembers: a special case for one customer, or the rounding method accounting relies on. These are rarely documented and live only in the code. In a full rewrite they get lost at some point, and you find out after launch.

Another problem is that while the team rewrites, the old system still needs bug fixes and features, so work is done twice. The business waits months without seeing anything, and if budget or patience runs out halfway, there is nothing usable to show for it.

The strangler approach: let the new grow around the old

The name comes from strangler fig trees that grow around a host tree and gradually replace it. In software it means building new capabilities beside the existing system and moving usage over one slice at a time, until nothing depends on the old system and you can switch it off.

Put a routing layer in front, such as a reverse proxy or API gateway, that decides which requests go to the old system and which go to the new one. Start with a slice that separates easily and shows clear value, such as product search or an order status page, rather than something tied to everything, like pricing.

Diagram of a gateway routing requests to the old or new system depending on which parts have moved.
Diagram of a gateway routing requests to the old or new system depending on which parts have moved.

Finding seams and adding an API layer

A seam is a join in the system where behaviour can be changed without editing everything around it: a boundary between modules, a separable set of database tables, or a file another system reads. Start by tracing who calls whom and where data flows, then pick the point where you can cut most cleanly.

Next, wrap the old system in an API layer so new screens, mobile apps and other systems call that layer instead of reaching into the database directly. The layer hides the mess behind it and lets you change the back end later without callers noticing.

Before touching old code, write tests that capture current behaviour, even where it looks odd, so you know immediately when the new version answers differently.

  • Map dependencies between modules and shared database tables.
  • Choose a first slice that is easy to separate and visible to users.
  • Write tests recording current behaviour before changing anything.
  • Add an API layer or gateway as the single front door.

Migrating data carefully

Data is often the hardest part, because old data is inconsistent: phone numbers typed ten different ways, or Thai names stored with the wrong encoding. Clean it and define a target format before moving, and rehearse the migration on real data several times before the actual day.

A safe pattern is to keep the old system as the source of truth at first, sync data to the new one, and compare results on both sides until you trust it. Only then switch the new system to be the source. After the move, keep the old system read-only for a while in case you need to go back.

A change you can undo is cheaper than a change that must be right the first time.

Running old and new side by side

The period when both systems coexist is the riskiest, and it usually lasts longer than planned. Use feature flags to show the new feature to a small group of users first and widen it gradually. If something goes wrong, you can switch back in minutes without deploying again.

Set up monitoring that compares both sides, such as order totals, error counts and response times, and set a real date for shutting down the old system. Otherwise both stay forever and you pay to maintain two of everything.

Key takeaways

  • Big-bang rewrites are risky because business rules hidden in old code get lost unnoticed.
  • The strangler approach builds new beside old and moves usage over one slice at a time.
  • Find seams and add an API layer first, backed by tests that record existing behaviour.
  • Migrate data by syncing and comparing before switching the source of truth.
  • Use feature flags and set a clear shutdown date for the old system.

FAQ

Sometimes, when the system is small, requirements are clear, and nobody can maintain the old technology. Even then, deliver in stages that are actually usable rather than one big release at the end.

Pick a slice that separates easily from the rest, has clear value to users or the business, and would not do much damage if it went wrong. That lets the team learn before touching core areas like pricing or accounting.

It depends on size and condition, so be wary of anyone promising a fixed number before looking at the system. The benefit of this approach is that you see results in stages and can adjust the plan along the way.

Legacy modernizationStrangler patternAPI layerData migrationRefactoring

Have a project like this in mind?

Tell us where you are today and we will suggest a sensible first step, with no obligation.

Talk to us