The Runtime Theory
Software Architecture

Monolith First: Why Simple Beats Distributed

Before microservices made sense for anyone, they were a cost center pretending to be scale. A review of when a monolith is the right architecture — and when the call for services is actually a cry for discipline.

The Runtime Theory Team3 min read#microservices#monolith#architecture#tradeoffs
On this page

Somewhere there is a team that split their application into a dozen services for the right reason at the right time. Most teams did it for a wrong one: "microservices are the modern architecture". This article is the case for the opposite default — one service, one repository of truth, split only when the seam is real and the measure is data.

What the architecture actually negotiates

Every architecture choice is a negotiation about what breaks when. A monolith makes three commitments:

  1. One deployable — every change ships together. Deployment coordination cost is close to zero; incident coordination is implied.
  2. One runtime graph — function calls stay calls. No serialization, no partial failures, no network timeouts between your own modules.
  3. One owner culture — a deploy that breaks the whole thing is everyone's problem, which is exactly the incentive structure you want in early years.

Services trade those for three different things:

  1. Independent deploys — a team can ship without asking anyone's permission.
  2. Independent failure domains — an errant component degrades part of the system.
  3. Independent scaling — one component at 100× load doesn't drag the rest.

Notice the trade is asymmetric: the costs of services (networks, serialization, version skew, distributed tracing, schema migrations across teams, rollback across services) are incurred immediately and permanently, while the benefits only materialize at specific scale/latency situations most applications never reach.

The numbers most architecture discussions never quote

The cost category chart people don't draw, because it's inconvenient:

text
operator                    monolith (1 deployable)   services (n deployables)
deploys/week                ~1, atomic                 ~n · n-1 coordination edges
rollback                    instant, one unit         orchestrating n services' history
request path                in-process call           actual TCP+TLS+queue round trips
failure mode               one blast radius           n² partial states to reason about
debugging distance         stack trace only           traces, logs, dumps across services
perf behavior              cache-friendly             serialization-dominated

Every row after line 1 is a physics difference, paid in milliseconds and incident minutes. The "it's basically the same code" framing of splitting is fiction: a monolith's p99 includes one process hop; a services' p99 includes the network, per hop, forever.

The three real reasons to split

The honest playbook — the one that survives contact with production — is: split only when a measured, specific constraint says so. The three constraints that actually justify it:

1. Independent scaling with divergent curves. One component grows 100× while siblings stay flat, and separate capacity management genuinely pays. (Autoscaling by CPU per service is the version you can measure.)

2. Independent failure domains. One component is allowed to be down part of the time — because a queue can tank, or a webhook can go to a flaky partner — without taking the rest of the product down. (The failure is already there in the monolith; services give it a boundary.)

3. Team-scale coordination. When independent deploys actually free two teams from a release train — meaning deploy permissions, ownership, and on-call align with the split — not when a diagram prettier is the motivation.

What splitting should feel like

A healthy split is a seam extraction, done in the worst-pain order, one at a time:

text
1. find the module that outgrew its owners (a team owns it; the rewrite is scheduled)
2. extract it as a *library first* — same code, different boundary within the monolith
3. then a service, behind a narrow interface
4. then let the new service's p99/inch app prove the move before anything else follows

The tell-tale sign of the wrong order: the team that split two services before drawing a single endpoint contract, and whose "architecture" now lives in a wiki that no code enforces — the strongest signal the whole exercise was ceremony.

When the page is finally honest

The famous retrospective (Graham, Lean Production blog — "microservices are a bet") earned its fame by saying the quiet part: 95% of companies should not implement microservices, and Amazon/Scalability folklore was a bad source of advice — because Amazon's scale is not a configuration you can copy; it's a constraint that emerged from measured demand.

None of this is "never". It is: default to one deployable; make the seam first; extract one component at a time; and require a measured, specific constraint — not a trendline — as the price of admission.