There is a comfortable myth in enterprise IT that says a fifteen-year-old core system is a wall you eventually knock down in one heroic weekend. Freeze the business, cut over to the new platform on Saturday night, reopen on Monday reborn. In practice, the big-bang rewrite is where budgets and reputations go to die. Most of them are quietly abandoned two years in, having delivered nothing but a parallel system that never quite caught up to the one still running the company.
So when a national retail chain asked us to help retire its aging ERP, our first commitment was the one that made everyone in the room uncomfortable: we were not going to turn it off.
The situation
The client operates roughly 60 stores across the region, all of them tied to a single monolithic ERP first deployed around fifteen years ago. Over time it had absorbed everything — inventory, pricing, purchasing, loyalty, store settlement, even a bespoke payroll bridge. Every integration reached straight into its database. Nobody dared touch the schema, because nobody could fully predict what would break, and the two engineers who understood its oldest corners had long since left.
It still worked. That was the trap. It ran the business well enough that the risk of change always looked larger than the cost of standing still — until stock-count nights started taking longer than the stores could absorb, and a payment-processor deprecation notice arrived with a hard deadline the monolith physically could not meet.
The challenge
The constraints were unforgiving. A retailer cannot close for a migration; every hour of downtime is lost revenue and lost trust at the till. The data was fifteen years deep and inconsistent, with years of workarounds baked into it. And the organization had, understandably, low appetite for risk after watching a peer company's own "modernization" collapse the year before. We needed a path that could be paused, inspected, and reversed at any point — not a leap of faith.
How we approached it
We ruled out the rewrite immediately and committed to incremental strangulation: build the new system around the old one, redirect one capability at a time, and let the monolith shrink until what remained could be switched off quietly.
- Map before touching. We spent the first weeks tracing every integration and reconstructing the real data contracts — not the documented ones — so we knew exactly what depended on what.
- Build a seam. We put an API facade in front of the monolith so that callers stopped talking to its database directly and started talking to an interface we controlled.
- Strangle by capability. Each domain — pricing, then inventory, then loyalty — was rebuilt as a service behind that facade and switched over independently, never all at once.
- Route with feature flags. Every cutover was a flag flip we could reverse in seconds, first for one pilot store, then a region, then the network.
- Reconcile continuously. Old and new ran in parallel, compared nightly, and any divergence was treated as a release blocker.
What we did
We began with pricing, the highest-pain, lowest-blast-radius domain, and stood it up as a standalone service reading from a synchronized copy of the data. For several weeks it ran in shadow mode — computing every price alongside the monolith and logging disagreements without affecting a single customer. Once the two agreed consistently, we flipped the flag for one pilot store, watched a full trading week, then widened the rollout region by region.
Inventory came next, then loyalty, each following the same rhythm: facade, shadow, pilot, expand. We hardened the seam along the way — adding MFA and proper access control at the API layer, centralizing logs into a SIEM so we could actually see behavior, and putting EDR on the store endpoints that had been quietly unmanaged for years. By the time the last domain moved, the monolith had been reduced to a thin settlement job that we retired over a single weekend — the only planned outage of the whole program, and a short one.
The outcome
| Metric | Before | After |
|---|---|---|
| Planned downtime for the migration | Estimated multi-day cutover | Under one weekend, one short window |
| Nightly stock-count / batch close | Ran past the overnight window | Roughly 3× faster, finished with margin |
| Time to change a price rule | Days, via specialist request | Same day, self-service |
| Integrations reaching the raw database | Nearly all of them | About half retired, rest behind the API |
| Recoverable cutover steps | Effectively none | Every step flag-reversible |
None of these numbers are dramatic on their own, and that is the point. The program's success was measured in the absence of events — no lost trading day, no stock-count crisis, no headline. The business kept selling while its foundations were replaced underneath it, and most of the store staff never knew a migration was happening at all.
What made the difference
- Reversibility beats confidence. We never trusted a cutover we couldn't undo in seconds. Feature flags turned every high-stakes decision into a low-stakes, observable experiment.
- Shadow mode earns trust cheaply. Running new and old side by side, comparing outputs before anything went live, converted the client's anxiety into evidence — and caught the data quirks that no amount of planning would have.
- Modernize the seam, not just the code. The facade was the right place to add the security controls — MFA, centralized logging, endpoint protection — that the old architecture had made impossible.
The monolith is gone, but the more durable outcome is that the next change no longer requires courage — only a flag. That is what modernization is actually for.