SE

Monolith vs microservices

Must-know concept1.3 hIntermediate

When to split, modular monolith as middle ground.

Microservices solve organisational problems, not technical ones. Splitting a service costs you transactions, atomicity, and easy debugging. You should only pay that cost when you're getting team-scaling or independent-deploy benefits in return. The modular monolith is the right answer for years.

The big idea

The "monolith vs microservices" axis is really two axes:

AttributeCode organisationDeployment unit
Big ball of mudNo modulesOne process
Modular monolithStrict module boundariesOne process
MicroservicesStrict module boundariesMany processes, independent deploys

The interesting move is from big ball of mud → modular monolith. Splitting into processes is the second step — only when modular boundaries aren't enough.

What microservices actually buy you

  1. Independent deployability

    Team A ships without waiting for Team B's release branch.

  2. Independent scaling

    The image-resizer needs 50 instances; the auth service needs 3.

  3. Technology heterogeneity

    The new ML service can be Python while everything else is Go.

  4. Failure isolation

    A bug in the recommendation engine doesn't take down checkout.

All four are mostly about teams. If you're one team of five, you're paying the cost without getting the benefit.

What you pay for

Microservices replace function calls with network calls. The bill:

Distributed transactions
Two services, one user action. No global commit. You need sagas or careful eventual consistency.
Network failure
Calls timeout, fail, retry. Every boundary gains circuit breakers and idempotency keys.
Observability
"What happened in this request?" becomes correlating across N services. Distributed tracing is non-negotiable.
Operational overhead
N services × M environments = service catalogue, dashboards, CI/CD pipelines, on-call rotations.
Local dev
Running 30 services on a laptop is its own engineering project.

The modular monolith — the middle ground

A single process, but with enforced module boundaries: each module exposes only its public API (interfaces, ports), persists into its own schema/tables, owns its tests. Inter-module calls go through those public APIs only.

API layerHTTP handlers
Orders modulepublic API
Payments modulepublic API
Inventory modulepublic API

Why this is great:

  • Atomic transactions across modules are still trivial — same DB.
  • Refactoring is a compile-checked operation, not a contract negotiation.
  • Easy to split later: a clean module boundary becomes a service boundary on demand.

When to actually split

Split a module out when you have at least two of these signals:

  • The module has its own scaling profile (10× or more different from the rest).
  • An independent team owns it and the shared-deploy cycle is friction.
  • It has a different technology fit (latency-sensitive ML, real-time streaming).
  • The bug-tolerance is different (this one must keep working).

If you have only one of these, fix the underlying problem (separate the deploy pipeline, profile the hot path) before adding a network boundary.

A practical migration path

  1. Find a seam in the monolith

    A module already loosely-coupled, used through a small interface.

  2. Extract a private API

    First in-process: replace direct DB access with calls to a module facade.

  3. Move the data

    Migrate that module's tables into its own schema; the facade now owns them.

  4. Add the network hop

    Replace the in-process facade with an HTTP/gRPC client of the same shape; deploy the new service.

  5. Add the resilience plumbing

    Timeouts, retries, circuit breaker, tracing — for this one call.

In practice

The teams that succeed with microservices usually went through years of modular monolith first. The teams that fail with microservices usually skipped that step. Start with a modular monolith; split when the pain forces you.

Key takeaways

  • Microservices solve team-scaling problems, not technical ones.
  • A modular monolith captures most of the benefits without the network cost.
  • Splitting trades simple function calls for distributed transactions, observability, ops overhead.
  • Split a module only when at least two of: independent scaling, team ownership, tech fit, blast-radius reasons apply.
  • Migrate gradually: extract a facade, move the data, then make it remote.

Checkpoint questions

Use these to test whether the lesson is clear enough to explain without rereading.

  1. 1What pain would justify splitting a monolith into services?
  2. 2Why is a modular monolith often the better first architecture?
  3. 3What new operational costs appear once services are split?
  4. 4Where should team boundaries, data ownership, and deployment ownership influence the design?

References

External resources for going deeper after the lesson above.