Illustrative example
Making an ageing .NET system safe to change again
An eight-year-old .NET application still ran a professional-services firm, but a one-line change took three weeks and only two people dared touch it. We modernised it incrementally — deployment and tests first — until change was cheap again, with no rewrite and no feature freeze.
- Sector
- Professional services
- Service
- Modernization
- Timeframe
- Assessment + 5 months incremental work
- C# / .NET
- ASP.NET
- SQL Server
- Azure DevOps
- xUnit
This is an illustrative example
It represents the kind of engagement we take on and the shape of outcome we work toward — a composite drawn from common situations, not a specific client. The company is anonymised, and the figures are representative targets rather than measured results. Real, client-approved case studies will replace these as engagements complete.
The problem
The application worked. It also frightened everyone. Releases were manual and took the better part of a day, so changes were batched, batches were risky, and releases grew rarer and larger — the classic doom loop. Two engineers understood the whole thing; one was close to leaving. The board had been quoted a rewrite with a very large number attached.
Constraints
- The business could not stop for a rewrite, and feature requests kept arriving.
- The knowledge was concentrated in two people, one a flight risk.
- Nobody could say, with evidence, where the real cost of change actually was.
What we did
We started with an assessment — the code, but also the deployment pipeline, the environments, and how work moved from idea to production. The finding was the common one: the binding constraint was not the architecture, it was the deployment. A team that cannot release confidently is slow at everything else.
So the sequence was deliberately unglamorous:
- Made it deployable. A repeatable build and an automated pipeline in Azure DevOps, before touching any structure.
- Put tests around what we were about to change. Characterisation tests that pinned existing behaviour — including the quirks — so change stopped being a gamble.
- Remediated versions and dependencies that blocked everything else.
- Introduced module boundaries inside the existing system, enforced by architecture tests in the build rather than by good intentions.
Only then did structural work become safe, and even then we strangled just the two components that genuinely needed it. The rest — stable, rarely changed — we left alone. Not everything old is debt.
Architecture
The destination was a modular monolith, not microservices. There was one team, one deployment pipeline, and no distributed-systems experience on staff — decomposing the system would have added a network in the middle of the difficulty they already had. Clear internal boundaries gave most of the benefit at none of the operational cost, and left the option to extract a module later if a real reason appeared.
// A boundary enforced in CI is worth more than any amount of documentation
// about the boundary.
[Fact]
public void Billing_does_not_reach_into_Scheduling_internals()
{
var result = Types.InAssembly(typeof(BillingService).Assembly)
.That().ResideInNamespace("Firm.Billing")
.ShouldNot().HaveDependencyOn("Firm.Scheduling.Internal")
.GetResult();
Assert.True(result.IsSuccessful);
}
Lessons
Every phase delivered standalone value, so the programme survived a mid-course change of priorities — which is exactly the property a rewrite lacks. By the end the team could change the system without fear, and the knowledge that had lived in two heads was in tests, records and a deployment anyone could run.
Representative outcomes
11 days → 2 days
median lead time for a change
manual → automated
deployment, from a day to minutes
0
big-bang rewrite; the system stayed in production throughout
Figures are illustrative targets for an engagement of this shape, not measured results from a specific client.
More selected work
Replacing paper job sheets for a field-service company
A 120-person field-service company ran its daily work on paper job sheets that were rekeyed by hand each evening. We replaced that with a mobile-first workflow platform, cutting the rekeying entirely and getting invoices out days sooner.
An AI document-extraction pilot that had to earn its place
A finance operations team drowning in supplier invoices wanted "AI". We ran a three-week sprint that defined the accuracy bar first, built a working extraction prototype on real documents, and evaluated it honestly against that bar — with a human in the loop and a clear fallback.