Part 0 · 2 chapters · ~12 min

Monolith, Modular Monolith, Services: Choosing Honestly

What each shape costs, Conway's law and team topology, the modular monolith as a default, signals that justify a split, the strangler fig pattern for extracting services, and recording the decision in an ADR.

1

Shapes and their costs

code
src/
  modules/
    ledger/      index.ts (public API)  internal/ …  migrations/ (schema: ledger)
    payouts/     index.ts               internal/ …  migrations/ (schema: payouts)
    identity/    index.ts               internal/ …  migrations/ (schema: identity)
  // eslint no-restricted-imports: modules/*/internal/** may only be imported inside its module
  // a database role per module: payouts cannot SELECT from ledger.* directly

Conway's law: systems copy the communication structure of the organisation that builds them. Draw the team map before drawing the service map; a service owned by three teams is owned by nobody.

THREE SHAPES, CHOSEN HONESTLY
what each costs and when it pays
monolithOne deployable, one database. Fastto build, one transaction. Hurtswhen many teams collide.modular monolithOne deployable, enforced moduleboundaries, a schema per module.Most of the benefit, little of thecost.servicesIndependent deploys and scalingper team. Costs: network failures,distributed data, platform work.team signalSplit when teams block each otheron deploys or ownership, notbecause of size alone.scale signalSplit when one part has verydifferent load, latency orisolation needs.the trapServices without separate data ordeploys: a distributed monolith(part 8).
swipe the figure sideways, or tap expand for full screen
1/5
monolith
A single deployable is the fastest way to build: one transaction, one debugger, one deploy. Shopify, Stack Overflow and Basecamp run large businesses on (modular) monoliths.
fastest start, simplest operationshurts with many teams
2

Extracting a service: the strangler fig

  1. Give the module a clean interface inside the monolith and route all callers through it.
  2. Give it its own schema and remove cross-schema joins (replace with API calls or read models).
  3. Stand up the new service behind the same interface; route a slice of traffic (by tenant or percentage) through a proxy.
  4. Migrate data with dual writes or CDC, verify with reconciliation, then cut over and delete the old code.
code
# ADR-014: Extract payouts into a service
Status: accepted · Date: 2026-03-02
Context: payouts deploys 6×/day, ledger 1×/week; payout spikes (salary day) need independent scaling.
Decision: extract via strangler fig; payouts owns payouts.*; reads ledger through its API.
Consequences: +1 network hop (p99 +8 ms budgeted); needs outbox for ledger events; on-call split.