The situation

The decision has been made — by you, by a predecessor, or by a slide deck from 2018. What matters now is that microservices are not a topology, they are a package deal. The independent-deployment benefit is real, and it is unlocked only by a set of capabilities that are individually boring and collectively expensive.

Pay part of the bill and you do not get a partial benefit. You get a distributed monolith: every cost of distribution, none of the independence. This page is the itemised bill.

Whether to split at all is Monolith First, Split on Evidence; where to draw the lines is Service Boundaries That Survive Reorgs.

The bill

Line itemWhy it is not optionalYou skipped it if…
Independent deploy per serviceThe entire benefit. If services ship together, you have a monolith with network latencyA release is a coordinated event with a spreadsheet
Data ownership — no shared databaseTwo services writing one table are one service with extra steps“Just add a column, both services read it”
Contract testingIntegration tests across N services do not scale; you need to break contracts at build timeYou find breakages in staging, or in production
Distributed tracing with propagated IDsA request crosses six processes; logs alone cannot reconstruct itDebugging means opening six dashboards and comparing timestamps
Per-service ownership, registered and enforcedAn unowned service is where security patches go to dieNobody can name the owner of two of your services
Timeouts, retries with backoff, idempotency, circuit breakersPartial failure is the steady state, not the exceptionA downstream slowdown becomes a full outage
A platform that makes a new service cheapIf standing one up takes two weeks, teams will not split — they will bloatNew services get built inside existing repos to avoid the paperwork
Per-service SLOs and on-callAggregate uptime is meaningless when failure is partialOne dashboard, one number, one very tired team

Read that table as a readiness checklist before splitting, and as a diagnosis afterwards. Every unchecked row names a specific, recurring production symptom.

The bill is not itemised so you can pick line items. It is itemised so you can see which one you skipped.

graph LR
    A["Independent deploy"] --> X
    B["Data ownership"] --> X
    C["Contract testing"] --> X
    D["Distributed tracing"] --> X
    E["Named ownership"] --> X
    F["Partial-failure handling"] --> X
    G["Cheap new service"] --> X
    H["Per-service SLO + on-call"] --> X
    X{"Paid in full?"}
    X -->|"every line item"| Y["Microservices<br/>teams deploy without each other"]
    X -->|"one line item short"| Z["Distributed monolith<br/>every cost, none of the independence"]

The arithmetic nobody runs beforehand

Availability multiplies. A request that synchronously touches six services at 99.9% each has a ceiling of about 99.4% — roughly four hours of monthly failure introduced by topology alone. The fix is not “make each service more reliable”; it is to stop making the chain synchronous. See Synchronous vs Asynchronous Integration.

Open the diagram full screen

Latency adds, and tails dominate. Six hops at a 10ms median look fine and then a p99 of 200ms somewhere in the middle becomes your p50 under load, because tail latency compounds along a chain in a way medians do not predict.

Coordination cost is why you did this. The benefit is measured in deploys not blocked on another team, not in tidiness. If you cannot show that number improving, the split is not working, whatever the diagram looks like.

When the style is right

  • Teams are blocking each other on deploys, repeatedly and measurably. This is the dominant legitimate reason and it is an organisational one.
  • A component has genuinely different requirements — 10× the traffic, a GPU, a different compliance regime, a runtime the rest of the stack cannot host.
  • Failure isolation is a stated requirement and you have demonstrated the monolith cannot provide it.

When the default is wrong

  • Fewer teams than services. More services than teams guarantees orphans. This is the most reliable predictor of a bad microservices estate.
  • No platform team, and no budget for one. The per-service infrastructure cost is paid N times by people who wanted to write features.
  • The boundaries are not stable. Splitting is hard to reverse; merging is easy. Under domain uncertainty, coarse modules inside one deployable let you be wrong cheaply.
  • A distributed transaction sits at the centre of the domain. If the core operation must be atomic across the proposed boundary, the boundary is wrong. Sagas and compensation are a real answer, but they are a business-visible answer — someone has to define what “refund the partially-completed order” means, and that someone is not an engineer.

What it costs

Every local refactor becomes a migration. Renaming a field used to be an IDE action. Now it is: add the new field, deploy, migrate consumers one by one, wait for the slowest team, remove the old field, deploy again. Multiply by every change that crosses a boundary. This single cost accounts for most of the velocity loss teams report after splitting.

Cognitive load per engineer goes up, permanently. Not just “more repos” — more pipelines, more dashboards, more runbooks, more versions of the shared library in flight simultaneously.

Infrastructure and spend multiply. Sidecars, per-service databases, load balancers, log volume from cross-service chatter. The cost line grows with service count, not with traffic.

Organisational change becomes architectural change. Boundaries aligned to teams decay when teams reorganise, and services do not move as easily as people do. Expect a re-alignment project roughly as often as you expect a reorg.

You cannot undo it quickly. The reversibility asymmetry is the whole reason to be conservative: merging two services is a week, splitting one is a quarter. Err toward fewer, larger services at every decision point.

See also

Last updated 28 Aug 2026, 00:00 UTC. history