title: "From Monolith to Microservices: My ERP Migration Journey"
date: 2026-04-17
readingTime: 5 min read
tags: [".NET", "Microservices", "Architecture", "Case Study"]
After 15 years of running a monolithic ERP system handling payroll, accounting, inventory, and trading for multiple companies, we made the call: it was time to modernize.
This isn't a theoretical architecture post. This is the story of what actually happened when we migrated a production ERP serving real businesses with real money and real deadlines.
Our legacy system was a classic ASP.NET Web Forms monolith:
The system worked. But it was fragile, slow to change, and expensive to maintain.
We didn't start with "microservices" as the goal. We started with problems:
Microservices emerged as a solution to these specific problems, not as an architectural ideal.
We didn't rewrite. We incrementally replaced:
┌─────────────────────────────────────────────────┐
│ Legacy Monolith │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ Payroll │ Account │ Inventory│ Trading │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ API Gateway / Reverse Proxy │
└─────────────────────────────────────────────────┘
│ │ │
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Payroll │ │ Inventory │ │ Trading │
│ Service │ │ Service │ │ Service │
│ (.NET 8) │ │ (.NET 8) │ │ (.NET 8) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└──────────────┴──────────────┘
│
↓
┌─────────────────┐
│ Legacy DB │
│ (shared temp) │
└─────────────────┘
We started with the Payroll Service—high value, well-understood domain, clear boundaries.
The hardest part wasn't code. It was data:
Before: One database, 500 tables, everything joined to everything.
After: Each service owns its data. No direct database access between services.
Payroll Service Accounting Service
┌─────────────────┐ ┌─────────────────┐
│ Employees │ │ General Ledger │
│ Salaries │ │ Journal Entries │
│ Deductions │ │ Chart of Accounts│
│ Tax Rules │ │ Financial Reports│
└─────────────────┘ └─────────────────┘
│ │
│ Events │ Events
↓ ↓
┌─────────────────────────────────────────┐
│ Message Bus (RabbitMQ) │
└─────────────────────────────────────────┘
Key principle: Eventual consistency. When payroll runs, it publishes PayrollProcessed events. Accounting subscribes and updates ledgers asynchronously.
We containerized everything:
# Dockerfile for Payroll Service
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
COPY . .
RUN dotnet publish -c Release -o /app
FROM base
COPY --from=build /app .
ENTRYPOINT ["dotnet", "PayrollService.dll"]
Kubernetes orchestration gave us:
You can't just use TransactionScope anymore. We implemented:
Debugging distributed systems requires new tools:
Eventual consistency is fine until it's not. We learned:
| Component | Technology | |-----------|------------| | Runtime | .NET 8 | | Containers | Docker | | Orchestration | Kubernetes | | API Gateway | YARP (Yet Another Reverse Proxy) | | Message Bus | RabbitMQ | | Database | SQL Server (per service) | | Caching | Redis | | Monitoring | Prometheus + Grafana | | Tracing | OpenTelemetry |
Don't split by technical layer (UI, API, DB). Split by business capability (Payroll, Inventory, Trading).
You can't debug what you can't see. Set up logging, tracing, and metrics before you migrate.
If you need strong consistency, you're building a distributed monolith. Accept eventual consistency and design for it.
Manual deployments at scale are painful. CI/CD isn't optional.
Some things stay monolithic. Our reporting module? Still monolithic. And that's fine.
Yes. But I'd start smaller, invest more in observability upfront, and resist the urge to microservice everything.
The goal isn't microservices. The goal is shipping value faster with less risk. Microservices are just a tool.
Have questions about specific parts of the migration? Drop a comment or reach out on LinkedIn.