← Back
Architecture 2 min read

Microservices without clear boundaries are a distributed monolith

Splitting a monolith does not remove coupling: it turns it into network calls. If the boundaries were not clear inside, they will not be clear outside either, and now every dependency costs latency and a brand-new failure mode.

Splitting a monolith does not remove coupling. It turns it into network calls.

If the boundaries were not clear inside the monolith, they will not be clear between services either — except now every dependency costs latency, a new failure mode and a coordinated deploy. The result has its own name in the literature: distributed monolith. It is why Martin Fowler recommends starting with a monolith and extracting services only once the boundaries have made themselves obvious.

The usual symptom: the monolith starts to hurt. Deploys drag. Merge conflicts are constant. Someone proposes "splitting it into services" and suddenly everyone agrees, because it sounds like a solution.

But it is not a solution. It is a change of problem.

The underlying mistake

When you split a badly structured monolith into microservices, what you are doing is distributing the coupling. Before, you had one module calling another directly. Now you have one service calling another over HTTP. The coupling is still there — except now it fails in production instead of at compile time.

Microservices do not fix the problem of blurry boundaries. They make it worse.

What real boundaries are

A well-defined boundary is one that encapsulates a complete business concept. Not a database table, not a technical entity — a functional domain with its own rules, its own language, its own lifecycle.

If you have an e-commerce system, Orders and Inventory are candidate boundaries. OrderRepository is not.

The difference matters because:

  • A real boundary can live as a module inside a monolith and work perfectly well
  • A real boundary can eventually become an independent service, if the team and the traffic justify it
  • A badly defined boundary will leak at the seams no matter what architecture you use

The right path

First, define the boundaries. Use Domain-Driven Design as a conceptual lens, not as an implementation recipe. Identify the bounded contexts, the ubiquitous languages, the real friction between teams.

Then implement those boundaries as modules in your monolith. One module per bounded context, with an explicit public interface and no direct dependencies between internals.

When the team grows or the traffic justifies it, extracting a well-bounded module into a service is a one-sprint task. Extracting spaghetti is a six-month saga.

When it does make sense

Microservices make sense when:

  • You have independent teams that need to deploy without coordinating
  • You have workloads with radically different scaling requirements
  • You have parts of the system with different lifecycles or SLAs

If none of those conditions apply, the modular monolith is the right architecture. It is not a second-rate architecture — it is the honest architecture for the problem you actually have.

Next · DevOps · 7 min GitHub Actions: from commit to deploy with no manual step Read next →