Architecture

From Monolith to Microservices: A Pragmatic Approach

15 min read
#architecture#microservices#dotnet#strangler-fig
Georg Pfeiffer
Georg Pfeiffer
Senior Solution Architect

"Let's rewrite it as microservices" is the most expensive sentence in software engineering. After leading several decomposition projects — some successful, some painful — here's my pragmatic guide to when and how to break apart a monolith.

When Microservices Are the Wrong Answer

Before reaching for microservices, ask: what problem am I actually solving?

Microservices don't fix:

  • **Bad code structure** — if your monolith is a mess, your microservices will be a distributed mess
  • **Slow deployments** — often a CI/CD problem, not an architecture problem
  • **Team communication issues** — Conway's Law cuts both ways

Microservices *do* help when:

  • Different parts of the system need to scale independently
  • Teams need to deploy and release independently
  • Technology choices need to vary by domain

The Strangler Fig Approach

Instead of a big-bang rewrite, extract services one at a time. The pattern:

  1. **Identify a bounded context** with clear boundaries
  2. **Build the new service** alongside the monolith
  3. **Route traffic** through a facade/gateway
  4. **Migrate data** incrementally
  5. **Cut over** when the new service is proven
text
┌─────────────────────────────┐
│         API Gateway         │
├──────────┬──────────────────┤
│          │                  │
│  New     │    Monolith      │
│  Service │    (shrinking)   │
│          │                  │
└──────────┴──────────────────┘

The crucial part: the monolith keeps working throughout. No big-bang cutover. No "we'll be down for maintenance" weekends.

Choosing What to Extract First

Pick a candidate that has:

  • **Clear domain boundaries** — minimal shared state with other areas
  • **Independent data** — its own tables, not joined to everything
  • **High change frequency** — you'll see the benefit of independent deployment quickly
  • **Low risk** — not your payment processing system on the first try

In practice, I've found notification services, reporting/analytics, and file processing make excellent first candidates.

Data: The Hard Part

The trickiest aspect of decomposition is data. A monolith typically has a single database with foreign keys everywhere.

Shared Database (Temporary)

As a transitional step, the new service can read from the monolith's database. This is explicitly temporary — it couples the service to the monolith's schema:

csharp
// temporary: direct database access
var orders = await _monolithDb.Orders
    .Where(o => o.CustomerId == customerId)
    .ToListAsync();

API Integration (Target State)

The target state is service-to-service communication via APIs or events:

csharp
// target: API call to order service
var orders = await _orderServiceClient
    .GetOrdersByCustomerAsync(customerId);

Event-Driven Data Sync

For data that multiple services need, publish change events:

csharp
// order service publishes events
await _serviceBus.PublishAsync(new OrderCreatedEvent
{
    OrderId = order.Id,
    CustomerId = order.CustomerId,
    Total = order.Total
});

// notification service subscribes and maintains its own projection

What I Wish I'd Known Earlier

  • **Start with a modular monolith.** Extract clean modules within the monolith before splitting into services. This forces you to define boundaries without the operational overhead of distributed systems.
  • **You need observability first.** Distributed tracing, centralized logging, and health checks should be in place *before* you split. Debugging a distributed system without observability is miserable.
  • **Network is not free.** Every service boundary is a network call. Latency, partial failures, and serialization overhead add up. Measure before and after.
  • **Team structure matters.** One team owning 12 microservices is worse than a monolith. Align service boundaries with team boundaries.

Key Takeaways

  • Don't start with microservices — start with understanding your domain boundaries
  • Use the Strangler Fig pattern for incremental migration
  • Data separation is the hardest part — plan for a transitional period
  • Invest in observability before decomposing
  • A well-structured monolith beats poorly designed microservices every time

Share this article

Georg Pfeiffer

About the Author

Georg is a senior solution architect specializing in .NET, Azure, and Dynamics 365. He helps organizations design and build scalable, maintainable enterprise systems. When he's not writing code, he's writing about it here.

Learn more about Georg

© 2026 georgpfeiffer.dev. All rights reserved.

Built with SvelteKit & Tailwind CSS