Legacy Modernisation 5 min read

Modernising a data platform for a telecom provider

Thirteen repositories, sixty endpoints and two hundred stored procedures replaced by one event-driven platform. Four months conventional, the rest AI-assisted.

A legacy platform had grown across thirteen repositories, roughly sixty endpoints and around two hundred stored procedures. Nobody held the whole picture. The work was to recover the business logic buried in it, and to replace it with something that produced the same outcomes from far fewer moving parts.

Context

A leading telecom provider, from August 2025, as architect through Sonat AS.

It is a classic pattern: an old system is given more and more responsibility over time, and grows to meet it. Business rules ended up mostly in the database, as stored procedures with no version history, no tests, and no boundary between one domain and the next. The behaviour was real and the business depended on it, but it was not written down anywhere except in the implementation.

That is the actual problem with a system like this. Not that the technology is old, but that the knowledge it encodes exists only in a form that resists reading.

My role

Architecture and system design, mentoring, and hands-on development.

Key decisions

Do the hard work before the code. The sequence was business process mapping, then distilling use cases, then writing specifications detailed enough to anchor implementation. This is the part that looks like delay from the outside and determines everything that follows.

Lift the business logic out of the database. Around two hundred stored procedures became a handful of versioned, testable, event-driven domain services. Logic that had been unversioned and untestable by construction became neither.

Consolidate rather than port. Thirteen repositories became one composable platform, and the API surface fell from roughly sixty endpoints to about thirteen — close to a fourfold reduction. The endpoints that survived were the ones that corresponded to something a consumer actually needed, which is only knowable once the use cases are written down.

Specification-driven AI development

The first four months were built conventionally. Everything after that was built with AI assistance, on the same codebase, with the same team, against the same specifications — which makes the comparison a reasonably controlled one.

The heaviest stretch fell in the second phase: from April to November 2026 the platform had to integrate with external partners and expose the new API surface, which is the part of a modernisation where the volume of work is highest and the room for interpretation is smallest. A partner integration either matches the agreed contract or it does not.

That is the work AI assistance suits, and only because the specifications already existed. The upfront design was not documentation produced for its own sake. It was the input that let AI tooling generate the right code rather than plausible-looking code — the failure mode you get when you point a capable generator at an underspecified problem and it fills the gap with something that compiles and reads well and is wrong.

That failure mode is the whole risk. A generator never signals that it is guessing; underspecified requirements come back as confident, idiomatic, incorrect implementations.

Review ran at two levels. Agents reviewed changes first, which is cheap enough to apply to everything and good at the mechanical faults — inconsistent error handling, missing cases, drift from the conventions already in the repository. Humans reviewed after. The ordering was deliberate: the agent pass clears the noise so the human pass is spent on the judgement calls rather than on things a machine can see.

But neither level closes the real gap, and it is worth being precise about why. Review of any kind catches code that contradicts the specification. It does not catch code that fills in what the specification failed to say — and an agent reviewer is worse placed than a human here, because it shares the generator’s blind spot. Ask a model to check work against an ambiguous requirement and it will resolve the ambiguity the same way the generator did, then report that the code is correct. Agreement between two models is not evidence.

So the real defence sits earlier: removing the ambiguity before generation. That is what the process mapping and use case work produced, and why specifications were reviewed more carefully than the code generated from them.

One thing we did not anticipate: the generated code got better as the repository matured. Early on there was little to imitate, and output tracked the specification and not much else. As conventions accumulated — error handling, event shapes, test structure — the existing code became context in its own right, and each new service arrived closer to the house style than the last. The codebase ends up functioning as a second specification, the one that covers everything too tacit to write down. Which is an argument for being deliberate about the first services you build, because they set the pattern everything after them is generated against.

High velocity was a consequence of the design work, not a substitute for it. The four conventional months are the reason the rest could move quickly: they are where the specifications were earned.

Tradeoffs

Some capabilities were deliberately retired rather than carried across, in order to automate the underlying tasks more effectively. The transition protected the interests of the primary consumers, but it did not protect everyone equally — some consumers had to adjust to the new shape of the platform.

A fourfold reduction in API surface is a real cost to somebody. Being explicit about who absorbs it is more useful than claiming the migration was free.

Outcome

  • Thirteen repositories consolidated into a single composable platform
  • Roughly sixty endpoints reduced to about thirteen
  • Around two hundred stored procedures replaced by a small set of versioned, testable, event-driven domain services
  • Legacy patterns replaced with modern event-driven equivalents
  • Materially improved test quality
  • Four months built conventionally, the remainder with AI assistance against specifications written first

The new platform is in production. The legacy system is scheduled for retirement in 2026 and is being switched off now — the point at which a modernisation stops being a parallel system and starts being the system.

Kotlin · Gradle · Azure · Kubernetes · OpenSpec · Distributed Systems · System Architecture · Software Design · Project Planning · Project Management

All projects