Skip to main content

On This Page

Modular Monoliths Win More Fights Than Microservices

4 min read
Share

TL;DR

Microservices can be the right answer, but they are often chosen for emotional reasons: status, scale theater, or a deep desire to debug network calls at 2 a.m. A modular monolith gives you most of the organizational benefits of separation without turning every local change into a distributed systems hobby. If your team is still small or your domain is still changing, that matters more than sounding grown-up.

The Seductive Lie of “Independent Services”

The microservices pitch is always charming:

  • teams deploy independently
  • scaling is surgical
  • services stay focused
  • the architecture is “future proof”

And in a perfect world, yes. In the same perfect world where every meeting ends early and every estimate is accurate.

In reality, microservices tend to introduce a new tax:

  • network latency becomes business logic
  • local debugging becomes archaeological work
  • versioning becomes a coordination sport
  • simple transactions become distributed sadness

Suddenly you are not just building software. You are operating a small internal internet.

What a Modular Monolith Actually Is

A modular monolith is one deployable unit with strong internal boundaries.

That means:

  • one codebase
  • one runtime
  • one deployment pipeline
  • many well-defined modules

Each module owns a slice of the domain. Modules talk through explicit interfaces, not by rifling through each other’s internals like raccoons in a pantry.

This is not “the big ball of mud, but with nicer comments.” The point is that boundaries exist in code and are enforced in review, imports, and tests.

Why This Usually Works Better

1. You Keep Transactions Simple

If two things must change together, keeping them in one process is not a moral failure. It is just practical.

Need to create an order and reserve inventory atomically? In a monolith, this is a transaction. In microservices, it becomes a saga, eventual consistency, retries, compensations, and a long Slack thread titled urgent-question-about-order-state.

2. Refactoring Stays Cheap

The best time to move a boundary is before you have paid for it in production incidents.

Inside a modular monolith, you can:

  • rename a module
  • move shared logic behind a clean interface
  • split a package
  • rewrite a hot path

without coordinating three deploys and a prayer.

3. Failure Modes Are Less Interesting

This is a compliment.

In a monolith, many failures are just failures. In microservices, failures are often ambiguous:

  • was it auth?
  • was it DNS?
  • was it a timeout?
  • was it the retry storm you accidentally built?

Less interesting failures are easier to fix. This is one of the few areas where boring is a feature.

How to Structure It

The easiest mistake is pretending folders are architecture. They are not. A folder named services does not stop anybody from importing everything from everywhere.

Use these rules instead:

  • modules own their data and logic
  • cross-module calls go through interfaces
  • shared code stays genuinely shared, not just convenient
  • dependencies point inward toward stable domain logic

Example:

src/
  billing/
    domain/
    application/
    infrastructure/
  catalog/
    domain/
    application/
    infrastructure/
  shared/

The shared/ folder should stay small. If it becomes a landfill of “common” helpers, that is not architecture. That is denial.

When to Split

Sometimes the monolith does deserve to die a graceful death.

Split a module into a service when:

  • it has a clearly independent lifecycle
  • its scaling profile is very different
  • multiple teams need to own it separately
  • the operational boundary is worth the extra complexity

Do not split because:

  • a consultant said “cloud-native”
  • the org chart has a new director
  • someone wants a diagram with more circles
  • the team is bored

Boredom is not a scalability requirement.

The Real Tradeoff

The real question is not “monolith or microservices?”

The real question is:

Do you want complexity to live in code, or in the network, the deployment pipeline, and the people who have to wake up when it breaks?

Most teams are better served by local complexity and global simplicity. A modular monolith gives you that balance.

Final Rule of Thumb

If your team cannot confidently explain the domain boundaries inside a monolith, microservices will not fix that. They will just make the confusion harder to see and more expensive to ship.

Build the boundary first. Break it into services later if reality insists.

Continue reading

Next article

Software Architecture Is Mostly About Boundaries

Related Content