Loading_

Migrating to Microservices: Lessons from a Successful Platform Rebuild

How I led a 2.5-year migration from a legacy monolith to microservices. 100% client retention, 50% scalability improvement, and a lot of unglamorous work that made it possible.

Migrating to Microservices: Lessons from a Successful Platform Rebuild

From 2021 to 2023, I was the Team Lead responsible for moving a legacy platform to microservices. Enterprise clients, strict SLAs, zero tolerance for downtime. If you mess up a project like this, you lose customers who took years to win.

We finished with 100% client retention and 50% improvement in scalability. But I want to be honest — it wasn’t because we were some kind of genius architects. We were just paranoid about risk and strict about scope.


Why we did it

The old platform worked. That’s important. It wasn’t broken. It just couldn’t keep up with what the business needed next.

But we couldn’t just rebuild from scratch. Enterprise customers had strict SLAs and documented processes. They don’t care about your architecture dreams — they care that their stuff keeps working. A big-bang rewrite would’ve been suicide. We needed something gradual.


The tech stack decision

I picked what the team already knew. React with TypeScript on the front end, Python and ASP.NET services on the backend, SQL Server for the database, Docker for deployments, Azure for hosting. Nothing exciting. That was the point.

I’ve watched projects fail because someone picked a trendy framework nobody on the team had shipped with. We had strong Python and .NET talent, clients already on Azure, SQL Server expertise in-house. Building on those strengths got us shipping faster and kept the risk down.


Running both systems in parallel

This saved us. Old system keeps running while we build the new one. No flag day, no big switch, no “pray it works” moment.

We split the work into three phases. First eight months: build the core 20% of features that 80% of users touch daily. Not full feature parity — just the stuff people actually use. We deliberately left out edge-case features and client-specific customizations. Ship, get feedback, iterate.

Months 9 through 14: data migration tools. The critical decision here was doing read-only migrations first. Copy a client’s data to the new system, let them look at it, confirm everything’s correct, and keep using the old system until they’re ready. Clients could see their data before committing. Built a lot of confidence.

Last phase: migrate clients in waves. Friendly beta users first, then medium accounts, then smaller ones, then the complicated holdouts. Each wave taught us something that improved the next one.


Things that actually worked

API gateway in front of everything. One entry point, handles auth and routing. The frontend team didn’t need to know about five different service URLs. Kept them sane.

We organized services by business domain, not technical layers. Recipe service handles recipes, ingredients, calculations — not separate services for CRUD vs validation vs calculations. Kept things simple and reduced the back-and-forth between services.

For long-running tasks we used message queues. Report generation, file processing, that kind of thing. Services don’t block waiting for responses, you get natural retries, and you can scale parts independently.

One controversial decision: we started with a shared database. “That’s not real microservices!” Sure. But it avoided distributed transaction nightmares and let us move faster. Splitting databases is a future problem. Doing it too early would’ve killed our velocity.


What bit us

Data migration was three times harder than we planned. Every time. Inconsistent date formats, orphaned records, business rules buried in data that nobody documented. We built validation, migrated in phases, reviewed edge cases by hand. Still found surprises.

We also started with too many services. Coordination became painful fast. Better to start with fewer, bigger services and split only when you have a real reason — different scaling needs, different team ownership.

Local dev experience was bad at first. Five services on every dev machine is painful. Docker Compose for one-command startup, hot reload everywhere, clear docs — that’s what fixed it. If developers can’t run the system locally, productivity drops off a cliff.

And honestly, we should’ve set up centralized logging and tracing from day one. With a monolith you have one log file. With microservices you have N log files across N services and debugging without correlation IDs is basically impossible.


The numbers

After 2.5 years: 100% client retention, zero data loss, 50% scalability improvement, faster deploy cycles, and the team stayed intact. No one quit during the migration, which honestly surprised me.


When it makes sense (and when it doesn’t)

Microservices are worth it if you have clear business domains, parts that need to scale independently, and enough engineers to own separate services. Don’t do it if your team is under 5 people, your domain boundaries are fuzzy, or you’re still figuring out what your product is. Just build a monolith.

The things that mattered for us: running both systems in parallel, starting with core features instead of chasing full parity, getting feedback from real users early, and investing in tooling before we desperately needed it.

The fancy architecture patterns honestly didn’t matter as much. What mattered was planning, communication, and boring infrastructure work. The rest follows.

Salih Yildirim

Salih "Adam" Yildirim

Full Stack Software Engineer with 6+ years of experience building scalable web and mobile applications. Passionate about clean code, modern architecture, and sharing knowledge.

{ ideas }
<thoughts/>
// discuss
</>{ }( )=>&&||
Gathering thoughts
Salih YILDIRIM

Let's Connect!

Choose your preferred way to reach out