Skip to main content
Article complete

Get one like this every Tuesday at 7 PM IST.

codewithmukesh
Back to blog
architecture dotnet 15 min read Lesson 116/149 New

When to Use Microservices (And When Not To)

A .NET architect's decision guide to microservices: what they actually solve, when to use them, when to avoid them, and why a modular monolith usually wins first.

A .NET architect's decision guide to microservices: what they actually solve, when to use them, when to avoid them, and why a modular monolith usually wins first.

architecture dotnet webapi-course

microservices microservices dotnet monolith modular monolith software architecture distributed systems system design clean architecture domain-driven-design ddd conways law event-driven-architecture api gateway dotnet 10 asp.net-core scalability architecture decisions service boundaries message queue database per service

Mukesh Murugan
Mukesh Murugan
Solutions Architect · Microsoft MVP
Chapter 116 of 149
View course

.NET Web API Zero to Hero Course

From dotnet new to docker push - REST, EF Core 10, auth, caching, Clean Architecture, observability. 149 hands-on lessons, source on GitHub.

Use microservices when independent deployment, team autonomy, or scaling one specific hot path is worth a real jump in operational cost. For most teams, the honest answer is “not yet.” A modular monolith gives you most of the structure with a fraction of the pain. Microservices solve an organizational and scaling problem, not a code-quality one - and confusing the two is how teams end up with a distributed mess that is harder to change than the monolith they were running from.

I have watched teams split a perfectly fine application into eight services because a conference talk said monoliths were bad, then spend the next year fighting network timeouts and broken local setups instead of shipping features. I have also seen a monolith buckle under a team of forty engineers who could not deploy without stepping on each other. Both are architecture failures. The trick is knowing which problem you actually have.

This article is the decision guide I wish more people read before drawing boxes and arrows. No giant code dump - this is about the judgment call. By the end you will know what a microservice really is, the exact signals that justify the move, the signals that scream “don’t,” the hidden costs you sign up for, and how real companies like Amazon Prime Video and Segment quietly walked their architectures back.

What Is a Microservice?

A microservice is a small, independently deployable service that owns one business capability and its own data, and talks to other services over the network. “Independently deployable” is the part that matters most. If you cannot ship one service to production without redeploying the others, you do not have microservices - you have a distributed monolith, which is the worst of both worlds.

Contrast that with a monolith. A monolith is a single deployable unit: one process, usually one database, all the code shipping together. That word gets used like an insult, but a well-structured monolith is a completely respectable way to run a large application. The opposite of “microservices” is not “bad architecture.” It is “fewer deployable units.”

A microservices architecture, then, is a system built as a collection of these services, each running in its own process, each deployable on its own schedule, each typically owning a private database that no other service reads directly. The boundaries between services are network calls (HTTP, gRPC) or messages on a queue, not in-process method calls.

That single design choice - turning method calls into network calls - is where every benefit and every cost of microservices comes from.

Comparison of an in-process method call in a .NET monolith versus the same call over the network in microservices, showing failure, consistency, debugging and local dev costs

What Problem Do Microservices Actually Solve?

Here is the most important idea in this whole article: microservices are primarily a solution to a people problem, not a code problem.

Sam Newman, who wrote the book most teams learn this from, is blunt about it: the strongest reason to adopt microservices is to let many teams work and deploy independently. This is Conway’s Law in action - your system’s structure will mirror your communication structure, so if you have many teams, splitting the system along team lines reduces how often they block each other.

Microservices buy you three concrete things:

  1. Independent deployment - Team A ships the payments service at 2 PM without waiting for Team B’s checkout release or a full regression of the whole app.
  2. Independent scaling - The image-processing service runs on 40 instances during a campaign while the user-profile service stays on 2. You scale the part that is hot, not the whole application.
  3. Technology and failure isolation - One service can use a different runtime or database, and a crash in the recommendations service does not have to take down checkout.

Notice what is not on that list: “cleaner code,” “better separation of concerns,” or “easier to understand.” You get clean boundaries from good design, not from network calls. You can write spaghetti across services just as easily as within a process - and now it is spaghetti you cannot debug with a single stack trace.

If your real problem is that your code is tangled, microservices will not fix it. They will distribute it across a network and add latency. Fix the boundaries first, in-process, where mistakes are cheap to correct.

Read next

Implementing Clean Architecture in .NET 10

How to build clean, enforced boundaries inside a single deployable app - the structure that lets you extract a microservice later if you ever need to.

When Should You Use Microservices?

Reach for microservices when you can check several of these boxes honestly. One box alone is rarely enough.

  • You have multiple teams blocking each other on deploys. This is the single strongest signal. When five teams share one release train and every deploy is a negotiation, splitting along team boundaries pays for itself.
  • One part of the system has wildly different scaling needs. A video transcoding pipeline, a search indexer, or a notifications fan-out that needs 50x the resources of everything else is a natural candidate to pull out and scale on its own.
  • Different parts have different availability or compliance requirements. Payments needs higher uptime and tighter audit boundaries than the blog CMS. Isolating it can be worth the cost.
  • You have a genuinely independent business capability with a stable boundary. Billing, identity, and inventory are classic examples - they have clear contracts and rarely need to share a transaction with everything else.
  • You already have the operational muscle. CI/CD, containerization, centralized logging, distributed tracing, and on-call rotations need to exist before you go distributed, not after.

If you find yourself nodding at three or four of these, microservices may genuinely be the right call. The move will hurt, but the pain buys you something real.

When Should You NOT Use Microservices?

This is the section most architecture content skips, so I will spend real time here. Avoid microservices when any of these are true:

  • You are a small team (under ~10 engineers). With one or two teams, the coordination problem microservices solve barely exists. You will pay all the operational cost for almost none of the benefit. A modular monolith is almost always the better bet.
  • You are still figuring out the product. Early-stage products change their domain boundaries weekly. Microservices freeze boundaries into network contracts and separate databases - exactly when you most need them to stay fluid. Moving a boundary inside a monolith is a refactor. Moving it across services is a migration.
  • Your services would share a database or a transaction. If two “services” constantly read each other’s tables or need to commit together, they are one service wearing a costume. You have coupled them at the data layer and lost every benefit of separation.
  • You lack the DevOps maturity. No container pipeline, no centralized logs, no tracing, manual deploys? Microservices will multiply every one of those gaps by the number of services you create. Ten services means ten things to monitor, deploy, secure, and debug at 3 AM.
  • Your performance bottleneck is the database, not the app tier. Splitting the app into services does nothing for a slow query. You will add network hops on top of the same slow database and call it progress.

The honest default for new projects and small teams is: do not start with microservices. Start with a well-structured monolith, keep the seams clean, and extract a service only when a specific, named pressure forces your hand.

What Hidden Costs Are You Signing Up For?

Every method call you turn into a network call inherits the eight fallacies of distributed computing. The network is not reliable, latency is not zero, and bandwidth is not infinite. Here is what that means in practice.

CostIn a monolithIn microservices
A cross-feature callIn-process method, nanoseconds, always succeedsNetwork call: can be slow, can time out, can fail mid-flight
Data consistencyOne database, one ACID transactionNo distributed transaction; you need sagas and eventual consistency
Debugging a requestOne stack traceCorrelated traces across N services and queues
Local developmentRun one project, press F5Run a fleet, or mock half of it (this is where .NET Aspire helps)
A schema changeOne migrationVersioned contracts, backward-compatible rollouts
DeploymentOne pipelineOne pipeline per service plus orchestration

The biggest one is data consistency. The moment each service owns its own database, you can no longer wrap a multi-step business operation in a single transaction. “Create order, charge card, reserve stock” stops being one commit and becomes a choreography of events that can each fail and must each be compensated. This is the saga pattern, and it is a large permanent tax on every workflow that crosses a service boundary.

The second is observability. In a monolith, a failed request is one stack trace. In microservices, that request might have touched six services and two queues, and you need distributed tracing (OpenTelemetry, correlation IDs) just to answer “where did it break?” If you cannot trace a request end to end across services, you cannot operate microservices safely.

Read next

Distributed Caching in ASP.NET Core with Redis

Once state is spread across services, shared caching and coordination become infrastructure you own. Here is how distributed caching works in .NET.

The Middle Path: Start With a Modular Monolith

For the vast majority of .NET teams, the right starting point is almost always the same: a modular monolith.

A modular monolith is a single deployable application split internally into well-defined modules with explicit boundaries. Each module owns its slice of the domain and exposes a clear public interface, and modules are not allowed to reach into each other’s internals. It still ships as one unit against (usually) one database. The discipline is logical, not physical: the boundaries are enforced in the code, not by the network.

That distinction is the whole point. You get the clean, decoupled boundaries that people think microservices give them, but you keep everything that makes a monolith pleasant - one deployment, one stack trace, real transactions, and a debugger that can follow a request from end to end.

Here is why it is the right default almost every time:

  • You get the benefit (boundaries) without the bill (the network). A call between modules is still an in-process method call. It is fast, it does not time out, and it commits in the same transaction. You spend zero of your budget on distributed-systems plumbing.
  • It keeps your options open. Architecture is about deferring decisions until you have the information to make them well. A modular monolith with clean seams keeps the microservices door open and cheap to walk through later. Premature microservices slam that door shut.
  • It matches how most teams actually scale. Few products need planetary scale or dozens of teams on day one. A modular monolith carries a small or mid-sized team comfortably for years, and it absorbs growth in features without forcing a distributed rewrite.
  • It fails gracefully. If your boundaries turn out to be wrong - and early on, they will - fixing them is a refactor inside one codebase, not a migration across services and databases. Mistakes stay cheap.

Martin Fowler called this the “MonolithFirst” strategy back in 2015, and a decade of industry experience has largely proven him right. You move toward microservices only when a specific, named pressure - team contention, a runaway scaling profile, a hard isolation requirement - outgrows the single deployment. Until that day arrives, the modular monolith is not a compromise. It is the correct answer.

Real-World Examples: When Teams Walked It Back

The strongest argument against defaulting to microservices is that several high-profile engineering teams adopted them and then publicly reversed course once the costs outweighed the benefits.

Why Did Amazon Prime Video Move Back to a Monolith?

In March 2023, the Amazon Prime Video engineering team published a now-famous post describing how they rebuilt their audio/video quality monitoring service. The original design was a distributed system of serverless components (AWS Step Functions and Lambda functions passing data around). It hit scaling limits and got expensive fast because of the orchestration overhead and the cost of moving data between components. They consolidated the whole thing into a single process - a monolith - and reported a roughly 90% reduction in infrastructure cost, along with better scalability. The team that literally helped popularize cloud-native patterns chose a monolith for that workload because the numbers said so.

Why Did Segment Go From Microservices Back to a Monolith?

Segment, a customer-data platform, wrote one of the most honest engineering retrospectives ever published, titled “Goodbye Microservices”. They had grown to over 140 microservices and found that the operational overhead - per-service deploys, dependency management, and the cognitive load of so many moving parts - was crushing their small team’s velocity. They consolidated back into a single monolith and got a dramatic productivity boost. Their lesson was not “microservices are bad.” It was “microservices were the wrong tool for the size of our team and the shape of our problem.”

Who Should Be Running Microservices, Then?

The companies that famously thrive on microservices - Netflix, Uber, Amazon’s retail platform - share a profile: thousands of engineers across hundreds of teams, traffic at planetary scale, and entire platform organizations dedicated to the tooling that makes distributed systems survivable. Shopify, often cited as a microservices shop, actually runs a large, disciplined modular monolith (a Ruby on Rails app) for its core, extracting services selectively. The pattern is consistent: these teams adopted microservices to solve an organizational scaling problem they actually had, with the operational investment to back it up.

If your situation does not look like that, copying their architecture copies their costs without their reasons.

What Architectural Decisions Matter If You Do Go Micro?

Say you have checked the boxes and the move is justified. These are the decisions that determine whether your microservices help or hurt.

  • Draw boundaries around business capabilities, not technical layers. A “payments” service that owns everything about payments is good. A “database service” or a “validation service” is not a microservice - it is a layer you have put on the network, and it will couple everything that uses it. Domain-Driven Design’s bounded contexts are the best tool here.
  • Give each service its own database. Database-per-service is non-negotiable. The moment two services share a database, they are coupled at the schema and can no longer deploy or evolve independently. This is also the decision that forces you into eventual consistency, so make it deliberately.
  • Prefer asynchronous messaging for cross-service workflows. Synchronous chains (service A calls B calls C) multiply failure and latency - if each is 99.9% available, a chain of five is only about 99.5%. Events on a message broker (RabbitMQ, Azure Service Bus, Amazon SQS/SNS) decouple services in time and contain failures. Use synchronous calls for queries, async events for state changes.
  • Put an API gateway at the edge. Clients should not know about your internal service topology. A gateway (YARP in the .NET world, or Ocelot for older stacks) handles routing, auth, and rate limiting in one place and lets you reshape services behind it without breaking clients.
  • Invest in observability before the second service. Centralized structured logging, metrics, and distributed tracing with OpenTelemetry are prerequisites, not nice-to-haves. .NET Aspire makes local orchestration and telemetry far less painful than it used to be.
Read next

Health Checks in ASP.NET Core

Liveness and readiness endpoints are the first piece of operational tooling every service needs before you add a second one. Here is how to wire them up in .NET 10.

Read next

Rate Limiting in ASP.NET Core

Rate limiting is one of the jobs an API gateway takes over at the edge. Here is how the built-in .NET 10 limiter works and where to apply it.

My Take: A Decision Matrix

After watching teams get this right and very wrong, here is the matrix I actually use.

Your situationWhat I’d build
New product, unclear domainMonolith. Keep it clean, move fast.
Growing app, 1-2 teamsModular monolith. Strong internal boundaries.
One feature needs very different scalingModular monolith + extract that one feature
Many teams blocking each other on deploysMicroservices along team boundaries
You want “clean code” or “modern architecture”Modular monolith. This is not a microservices problem.
Planetary scale, hundreds of engineersMicroservices, with a platform team to support them

The decision is not “monolith vs microservices.” It is “what is the smallest number of deployable units that solves the problem I actually have right now?” For almost everyone reading this, that number is one - a clean, modular one - until a specific pressure proves otherwise. Every service you add before that pressure arrives hands you a bill you keep paying.

Read next

Repository Pattern: Do You Really Need It?

The same question, one layer down. A pattern is worth its cost only when you can name the pressure it relieves - here is that argument applied to repositories.

Key Takeaways

  • Microservices solve an organizational and scaling problem, not a code-quality one. If your code is tangled, fix the boundaries in-process first.
  • A microservice is defined by independent deployability and a private database. If services share a database or must deploy together, you have a distributed monolith - the worst case.
  • Default to a modular monolith. It gives you clean boundaries and one deployment, and it keeps extracting a real service later cheap and contained.
  • Every network boundary costs you consistency, observability, and local-dev simplicity. Sagas and distributed tracing become permanent taxes on cross-service work.
  • Real teams (Amazon Prime Video, Segment) reversed course when microservices cost more than they returned. Adopt them for reasons you can name, with the operational maturity to support them.

Frequently Asked Questions

What is the difference between a monolith and microservices?

A monolith is a single deployable application where all code ships and runs together, usually against one database. A microservices architecture splits the system into small, independently deployable services that each own a private database and communicate over the network. The key difference is the number of deployable units and whether parts can be released and scaled independently.

When should I use microservices?

Use microservices when multiple teams are blocking each other on deployments, when one part of the system needs to scale very differently from the rest, when parts have different availability or compliance needs, and when you already have CI/CD, containerization, centralized logging, and distributed tracing in place. One reason alone is rarely enough; you want several of these to be true.

When should I avoid microservices?

Avoid microservices when you have a small team, when your product domain is still changing often, when your services would share a database or a transaction, when you lack DevOps maturity like CI/CD and tracing, or when your real bottleneck is the database rather than the application tier. In all these cases a modular monolith delivers more value for far less operational cost.

Is a modular monolith better than microservices?

For most teams, yes, at least to start. A modular monolith gives you clean internal boundaries and a single deployment with none of the network, consistency, and observability costs of distributed services. It also keeps the option to extract a true microservice later cheap, because the boundaries already exist. You only pay the microservices tax for the specific module that genuinely needs independent deployment or scaling.

Why did Amazon Prime Video move from microservices back to a monolith?

In 2023 the Amazon Prime Video team rebuilt their audio/video quality monitoring service, which was originally a distributed serverless design using AWS Step Functions and Lambda. The orchestration overhead and the cost of moving data between components limited scaling and raised cost. Consolidating it into a single process cut infrastructure cost by roughly 90 percent and scaled better for that specific workload.

Do microservices make my code cleaner?

No. Clean code comes from good design and clear boundaries, which you can achieve inside a single application. Microservices turn in-process method calls into network calls, which adds latency and failure modes without improving the quality of the code itself. If your code is tangled, splitting it across the network just distributes the tangle and makes it harder to debug.

Does each microservice need its own database?

Yes. Database-per-service is a defining rule of microservices. If two services share a database, they are coupled at the schema and can no longer be deployed or evolved independently, which removes the main benefit of the architecture. Owning a private database is also what forces you to design for eventual consistency using patterns like sagas.

Can I start with a monolith and move to microservices later?

Yes, and for most teams that is the recommended path. Start with a well-structured modular monolith, keep module boundaries explicit, and extract a module into its own service only when a specific pressure such as team scaling or independent scaling appears. Martin Fowler called this the MonolithFirst strategy, and it keeps the migration contained rather than turning it into a full rewrite.

Summary

Microservices are a powerful tool for a specific problem: letting many teams deploy and scale independently at large scale. They are not a default, a sign of seniority, or a fix for messy code. The cost is real and permanent - distributed data, eventual consistency, harder debugging, and a local-dev story you have to engineer.

For the vast majority of .NET applications, the right move is a modular monolith: clean internal boundaries, one deployable unit, and the freedom to extract a real service the day a real pressure demands it. Amazon Prime Video and Segment both made that call after living with the alternative. Borrow their conclusion without paying their tuition.

Pick the smallest number of deployable units that solves the problem in front of you today. That number is usually one - and a well-structured one will carry you a lot further than the internet wants you to believe.

Happy Coding :)

View all articles

What's your take?

Push back, share a war story, or ask the obvious question someone else is wondering. I read every comment.

View on GitHub

Weekly .NET tips · free

Newsletter

stay ahead in .NET

One email every Tuesday at 7 PM IST. One topic, deep. The week's articles. No filler.

Tutorials Architecture DevOps AI
Join 9,735 developers · Delivered every Tuesday
Privacy notice 30s read

Cookies, but only the useful ones.

I use cookies to understand which articles get read and which CTAs actually work. No third-party advertising trackers, ever. Read the privacy policy →