Architecture

Modular monolith or microservices: choosing on team shape

Microservices solve organisational scaling problems and introduce distributed systems problems. Most teams should start with a well-structured monolith.

Key takeaways

  • Microservices solve organisational scaling, not code quality.
  • Distributed systems costs are real and permanent.
  • A modular monolith gives the structure without the overhead.
  • Extract services when there is a specific reason, one at a time.

What microservices actually solve

Independent deployment by independent teams, and independent scaling of components with very different load profiles. Both are real problems at a certain organisational size.

They are not a solution to messy code. A poorly structured monolith becomes a poorly structured distributed system with network calls between the mess.

The costs you take on

  • Network calls fail; every boundary needs retries, timeouts and idempotency.
  • Transactions across services require compensating logic.
  • Debugging spans services and needs distributed tracing.
  • Local development becomes considerably harder.
  • Deployment, monitoring and on-call multiply.

The modular monolith

One deployable unit with strict internal module boundaries: modules communicate through defined interfaces, own their data, and do not reach into each other's tables.

This gives most of the structural benefit — clear ownership, testable boundaries, the ability to reason about a part — without distributed systems overhead. It also makes later extraction straightforward, because the seams already exist.

Extract when there is a reason

Good reasons: a component with a genuinely different scaling profile, a team that needs to deploy independently, a part with different compliance requirements.

Extract that piece and leave the rest. A gradual, evidence-led split ends up in a better place than a big-bang decomposition designed before the pressures were known.

Enforcing module boundaries

A modular monolith only delivers its benefit if the boundaries hold, and in a single codebase nothing physically prevents one module reaching into another.

Enforce it in the build: package or namespace rules, dependency linting, or architecture tests that fail when a module imports another's internals. Without enforcement, boundaries erode within months under deadline pressure.

Give each module its own data ownership too. Shared tables are the most common way a modular design quietly becomes a monolith in the pejorative sense.


Frequently asked questions

When you have more services than teams, coordination cost usually exceeds the benefit. Services without a clear owner drift.

Yes, and some organisations do. Consolidating services that always deploy together is a legitimate simplification.

The same reasoning applies. Many small functions with no clear boundaries produce the same coordination problems with additional cold-start considerations.

Working on this?

A conversation with our team, no charge and no obligation.

Book a conversation