Back to blog
Backend Systemsintermediate

Monolith vs Microservices — Make the Right Call

A monolith is not a bad word. Understand what each architecture actually costs, when microservices are worth the pain, and why starting with a modular monolith is almost always the right move.

LearnixoApril 14, 20265 min read
.NETC#ArchitectureMicroservicesMonolithSystem DesignDistributed Systems
Share:𝕏

A Monolith Is Not a Ball of Mud

The word "monolith" gets used as an insult. It shouldn't be. A monolith is a single deployable unit. It can be:

  • Well-structured with clear internal boundaries
  • Easy to run locally — one dotnet run, everything works
  • Easy to test — no mocks of HTTP services, no Docker Compose just to run unit tests
  • Easy to trace — one process, one log stream, one profiler session
  • Easy to refactor — rename a method, the compiler tells you everywhere it's used

The monolith's real problem is uncontrolled coupling — not the deployment model. A monolith where every class knows about every other class is a problem. A monolith with clean module boundaries is not.


What Microservices Actually Cost

Microservices are not free. Each boundary you draw at the network level adds:

Latency. A method call takes nanoseconds. An HTTP call takes milliseconds. Multiply by every inter-service call in a request, and a user action that touches 4 services might accumulate 50-200ms of pure network overhead.

Distributed transactions. You can't wrap two database operations in a using var tx = db.BeginTransaction() when those databases are in separate services. You need sagas, two-phase commit, or eventual consistency — each of which adds complexity and failure modes.

Operational overhead. Each service needs:

  • Its own CI/CD pipeline
  • Its own health check endpoint
  • Its own logging configuration and correlation ID propagation
  • Its own infrastructure (container, deployment manifest, config secrets)
  • Service discovery so other services can find it
  • Retries and circuit breakers for when it goes down

Testing complexity. Unit tests are fine. Integration tests now require either mocking other services (fragile) or spinning up multiple services (slow and complex). Contract testing with Pact helps but adds another tool to maintain.

Debugging complexity. A 500 error in service A caused by a bad response from service B triggered by a timeout in service C requires distributed tracing to diagnose. In a monolith you get a single stack trace.


The Fallacies of Distributed Computing

Peter Deutsch and James Gosling identified these in 1994. They remain true:

  1. The network is reliable
  2. Latency is zero
  3. Bandwidth is infinite
  4. The network is secure
  5. Topology doesn't change
  6. There is one administrator
  7. Transport cost is zero
  8. The network is homogeneous

Every microservices architecture must account for all eight. Your monolith gets most of them for free.


When the Monolith Wins

Small team. A team of 3-5 engineers does not benefit from microservices. The operational overhead consumes engineering bandwidth that should go to product features.

Unclear domain. If you don't know where the boundaries are yet, drawing service boundaries early guarantees you'll draw them wrong. Refactoring a distributed system is far harder than refactoring a monolith.

Early stage product. Requirements change fast. In a monolith, changing a data model propagates through the type system — the compiler catches it. Across services, you're versioning APIs and coordinating deployments.

Simple scaling requirements. If your bottleneck is database reads, a read replica solves it. If it's CPU, add instances of the monolith. Horizontal scaling of a well-designed monolith handles substantial load.


When Microservices Actually Help

Microservices solve specific, real problems. Use them when you have those problems:

Team autonomy. When two teams need to deploy independently without coordinating, a service boundary gives them that. Conway's Law in action — your architecture mirrors your org chart.

Independent scaling. If the image processing service needs 16 CPUs and the user service needs 0.5, separating them is cost-effective. If every service scales together, you've added overhead for nothing.

Different technology requirements. The ML model serving service needs Python and GPU access. The rest of your backend is .NET. Separate services make sense.

Compliance isolation. Payment processing under PCI-DSS, health records under HIPAA — isolation can be a regulatory requirement, not just an architectural preference.

Proven domain boundaries. If you've built the monolith and the domain boundaries are clear, stable, and the team has outgrown the shared codebase, extraction is a rational next step.


The Migration Path: Start Modular

Don't start with microservices. Start with a modular monolith — one deployment, hard internal boundaries:

Phase 1: Modular Monolith
  ├── Orders module (internal)
  ├── Inventory module (internal)
  ├── Notifications module (internal)
  └── Single deployment — dotnet run

Phase 2: Extract When Needed
  ├── Orders service (HTTP + own DB)       ← high traffic, team owns it
  ├── Inventory service (HTTP + own DB)    ← separate scaling needs
  └── Notifications still in monolith      ← no reason to extract yet

The modular monolith forces you to define boundaries (module contracts, integration events) before splitting. When you do split, you're refactoring — not redesigning.


The Decision Framework

Ask these questions before choosing microservices:

| Question | If Yes → Consider Microservices | |---|---| | Do we have 3+ teams that need to deploy independently? | Team autonomy | | Does one component need to scale 10x more than others? | Independent scaling | | Does one component have different tech requirements (GPU, Python)? | Tech heterogeneity | | Do we have compliance boundaries requiring process isolation? | Regulatory | | Have we already built and understood the domain in a monolith? | Proven boundaries |

If you can't answer yes to at least two, build the monolith first. You can always split later. You can't easily unsplit.


An Honest Summary

Most applications that are microservices today would be better served by a well-structured monolith or modular monolith. The exceptions are real — but they're exceptions.

The teams that benefit most from microservices are large organizations with proven domains, multiple autonomous teams, and the platform engineering capability to absorb the operational cost. If that's not you yet, build the modular monolith and spend the saved complexity budget on product quality.

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.