Modernisation

Retire the old system, one slice at a time

For the system that still earns money and nobody wants to touch. A routing layer goes in front, capabilities move onto a modern stack one at a time, and old paths get switched off.

  • No big-bang cutover
  • Parity before traffic
  • Decommission per wave
  • 30-day warranty

Where modernisation actually goes wrong

Legacy rarely means old syntax. It means behaviour nobody can describe any more: the rounding rule in the invoicing job, the nightly file three downstream teams silently depend on, the flag that exists because of an incident in 2014. The code is the only remaining specification and the people who wrote it have moved on. That undocumented behaviour, not the codebase, is the asset you are actually migrating — and it is the thing a rewrite quietly agrees to reproduce without ever writing it down.

Full rewrites fail on a requirement nobody can write down: do everything the old system does. Meanwhile the business keeps changing the old system, so the target moves for as long as the build runs. We work in waves instead. A routing layer goes in front and forwards everything unchanged when it lands; each wave then takes one capability, rebuilds it, proves parity against the old path under real traffic, flips the route and deletes what it replaced.

Constraints that shape Legacy Application Modernization work, what each forces, and how the studio responds
What is true of this workWhat it forcesWhat we do about it
The code is the only specificationBehaviour must be captured firstCharacterisation tests before any rebuild
The old system keeps changingThe migration target movesShort waves, one slice each
One database, many hidden writersSlices cannot own data cleanlyChange data capture, single writer
Batch jobs nobody documentedFailures surface days after cutoverBatch scheduled as first-class waves
Hybrid state costs two architecturesWaves must actually retire somethingNamed kill list per wave

In scope

  • Routing seam and per-route traffic control
  • Characterisation tests over existing behaviour
  • Slice rebuild with its own schema
  • Cutover, rollback and decommission per wave

Not in scope

  • Bulk one-shot data platform migration
  • Long-term running of the new stack
  • Rewriting the front end from scratch

Handled by

What we build

01

Behaviour archaeology

We read the code, trace production traffic and interview whoever is left. LLM-assisted summarisation speeds comprehension, but nothing enters the migration plan until an executable test proves it.

Characterisation test suite
02

Seam and routing layer

The gateway that makes incremental migration possible. It fronts the legacy application, holds the old URL and payload contracts intact, and lets us move one route at a time unnoticed.

Live routing seam
03

Database decoupling

Shared tables are what block a slice. It moves to its own schema, change data capture keeps the monolith in sync rather than dual writes, one writer per table enforced.

Change data capture sync
04

Parity and shadow running

Before traffic switches, the new path runs on mirrored requests with writes suppressed while a reconciler compares outputs field by field. Mismatches are triaged as legacy or new bugs before cutover.

Reconciliation report
05

Batch and integration rescue

Overnight jobs, SFTP drops, fixed-width files and the report finance still opens on a Monday. Undocumented and load-bearing, so they are scheduled as first-class waves rather than found during cutover weekend.

Rebuilt scheduled jobs
06

Decommission and exit

Turning the old thing off is a deliverable, not a hope. Each wave ends with servers deprovisioned, licences cancelled, scheduled jobs removed and dead code deleted from the repository.

Decommission record

How the work runs

  1. 01Weeks 1–2

    Map and capture

    We inventory every entry point, batch job, integration and database object, then trace which actually run. Dead routes are marked for deletion instead of migration, and the first slice is wrapped in characterisation tests.

    You getSlice map, kill list, characterisation tests

  2. 02Weeks 3–4

    Seam and first slice

    The routing layer goes in front of the legacy application, proved transparent under production load, then the first slice is rebuilt behind it. Fourteen days in, new code serves one real route in a demo.

    You getLive routing seam plus one slice

  3. 03Weeks 5–6

    Shadow and cut over

    Live traffic is mirrored to the new path with writes suppressed, and outputs are reconciled until the diff is empty or every difference is explained. Traffic then shifts in stages, the old path staying warm.

    You getReconciliation report and cutover runbook

  4. 04Weeks 7–8

    Retire and repeat

    We delete the legacy path, drop the tables it owned, cancel the licence, remove the cron entry and record what the wave killed. The next slice reuses the seam and test harness already in place.

    You getDecommission record per wave

What you are handed

  • Slice map with dependency and kill list
  • Characterisation test suite over legacy behaviour
  • Routing seam with per-route traffic control
  • Rebuilt services with their own schemas
  • Change data capture sync and reconciler
  • Cutover and rollback runbook per wave
  • Decommission record with retired assets
  • Infrastructure as code for the new stack
  • Handover walkthrough and 30-day warranty

Typical stack

Seam and routing

NGINXEnvoyKongOpenAPI 3Unleash

Rebuild targets

Django + DRF.NETSpring BootNode / TypeScriptPostgreSQLRedis

Data movement

DebeziumKafkaAWS DMSpgloaderFlyway / Liquibase

Proof and platform

PlaywrightTestcontainersTerraformKubernetesGitLab CIOpenTelemetry

The calls we make, and why

Should we rewrite it or strangle it?

Strangle it while the system is live and still changing

A rewrite freezes your only working specification while the business keeps editing it, so the target moves for the whole build. Working behind a seam keeps every wave small, reversible and shippable, and you can stop after any of them.

We’d choose otherwise whenthe app is small, one team owns the whole domain, and the runtime is unsupported

Microservices, or a modular monolith?

A modular monolith by default, services only where justified

Most legacy pain is coupling and untested code, not deployment topology. Splitting a system you do not yet understand into twenty network calls converts logic bugs into distributed ones, which are far harder to reconcile during a migration.

We’d choose otherwise whena slice needs its own scaling profile, release cadence or compliance boundary — usually a handful

Can AI just translate the old code for us?

AI reads the legacy code; it does not ship the migration

We use it where it is strong: summarising modules, recovering business rules, drafting characterisation tests. Line-by-line translation preserves the structure you were paying to escape, and a plausible translation of a rounding rule looks correct until month-end reconciliation fails.

We’d choose otherwise whenthe target is a like-for-like port of a small, fully tested module with no business rules

Should the new system keep the old behaviour, including the bugs?

Keep the bugs through cutover, fix them straight after

Parity is the only cheap way to prove a migration is safe. Change behaviour and outputs at the same time and every mismatch becomes an argument instead of a defect, which is how cutovers stall in review.

We’d choose otherwise whenthe old behaviour is a compliance or security problem — then it ships as a tracked change

This fits if

  • The system still earns but every change takes weeks and frightens someone
  • A platform or database version loses support on a known date
  • You need to keep shipping features while the migration runs
  • Nobody left can explain what the nightly batch actually does
  • You want the old stack switched off, not running beside the new

Look elsewhere if

  • You want the whole rewrite priced before anyone reads the code
  • Nobody internal can be freed to answer questions about old behaviour
  • A packaged product already covers your domain — buy it instead
EngagementFixed-scope waves
Typical lengthWaves of 4–6 weeks
How it startsA paid two-week assessment: we read the code, map the slices and price the first wave.

Questions we get asked

How long before we see anything running?

Fourteen days into the first wave you get a working demo: the routing seam live in front of your application and one real capability served by new code, with the old contract intact. That first slice is chosen to be genuinely used but not business-critical, so the mechanism is proved on real traffic before anything that matters moves.

Why won’t you quote the whole modernisation up front?

Because the estimate would be fiction and you would still pay the difference. Nobody can price twenty years of undocumented behaviour from the outside. We price a fixed-scope assessment, then price each wave once its slice has been read and tested. You approve wave by wave and can stop after any one, with working software in production.

What happens if a cutover goes wrong in production?

The route flips back, usually in seconds. Nothing is deleted at cutover — the legacy path stays deployed and warm behind the seam until the new one has run clean for an agreed period. Every wave ships with a rollback runbook naming the switch, the owner, and the data repair steps if writes have already landed.

Who owns the code, and are we locked into you afterwards?

You own all of it — source, infrastructure definitions, tests and documentation transfer to you in full. We build on standard platforms and avoid anything only we can operate. The seam itself is ordinary gateway configuration. Handover includes a walkthrough with your engineers plus a 30-day warranty on defects in what we shipped.

You mirror live traffic to test parity. What happens to our production data?

It stays inside your boundary. The shadow path is deployed in your own infrastructure and accounts, writes are suppressed so nothing reaches downstream systems or third parties, and the reconciler runs there rather than on our machines. For regulated fields we compare hashes instead of storing raw values, and diff records are kept only until the mismatch closes.

Tell us the requirement.

Thirty minutes with the engineers who would build it. You leave with a scope, a timeline and a fixed price — or an honest no, and the reason why.