Case pattern | Mid-market, AU
Migrating a legacy .NET system to a cloud-native architecture.
A staged migration from a long-lived .NET monolith into a cloud-native architecture, with zero customer-visible downtime. The interesting parts: how we picked the seams to cut along, how we held two versions of the truth in parallel during the transition, and what we'd do differently if we started over.
By Solyntra Engineering
The starting point
A mid-market Australian business with a .NET monolith that had been in production for over a decade. The system worked, but it was increasingly difficult to change. New features took months to ship. Deployments were risky and required weekend windows. The team spent more time on maintenance than new development.
Finding the seams
The first step wasn't writing code. It was understanding where the natural boundaries in the system lay. We mapped data flows, identified bounded contexts, and looked for places where the coupling was weak enough to cut.
The goal wasn't to rebuild everything at once. It was to identify the smallest piece we could extract that would deliver value and prove the pattern. We started with a reporting subsystem that had clear inputs and outputs and minimal dependencies on the rest of the system.
The strangler pattern
We used the strangler fig pattern: build the new system alongside the old one, route traffic incrementally to the new system, and gradually retire the old one. This approach lets you migrate without a big-bang cutover and without downtime.
Dual writes
During the transition, we wrote to both the old and new systems. This let us validate that the new system produced the same results as the old one before we switched reads over.
Feature flags
We used feature flags to control which users hit the new system. We started with internal users, then expanded to a small percentage of production traffic, then gradually increased until the new system handled everything.
Comparison testing
We logged the outputs from both systems and compared them automatically. When they diverged, we investigated. Most divergences were bugs in the new system. Some were bugs in the old system that we'd been living with for years.
The cloud-native target
The new architecture used containerised .NET services deployed to Azure Kubernetes Service. Each service owned its own data and exposed a well-defined API. We used Azure Service Bus for async communication and Azure SQL for persistence.
Critically, we didn't try to adopt every cloud-native pattern at once. We started with containers and CI/CD. We added observability. We added service mesh later when we needed it. Each step was justified by a specific problem we needed to solve.
What we'd do differently
We underestimated the data migration complexity. Moving data between schemas while both systems are live is hard. We'd invest more in automated comparison testing earlier.
We'd also be more aggressive about deleting code from the old system as we migrated. We left too much dead code around "just in case," which made the codebase harder to navigate.
Modernising a legacy system?
We can help you plan and execute the migration without the big-bang risk.
Book a discovery call