Topic 77

What Is Beyond One Service

Architecture

Stagedoor is one service with one database, and the book has argued for keeping it that way at every turn: one codebase that the api and the worker both run, one transaction boundary inside which the seat and the order change together, one place to read an order's state. Every hard problem in the preceding seventy-six topics happened inside that shape, and each took a chapter to solve properly. The last topic is honest about the day the shape stops being enough: the schema that three teams contend over, the on-sale load that no single primary can write, the render pipeline that deserves its own deploy cadence. It names the signals, walks the first split, prices the hard part, and hands the reader to the two courses that begin where this one ends.

It is a map of costs, not a recommendation. The recommendation is the sentence before it: one service, built properly, for as long as the signals allow. Chapter 1 drew the line around this book and promised that its last topic would be the honest moment where one service is no longer enough; this is that topic, and the honesty cuts both ways.

The Signals

Four things, and only these four, are reasons. Deploys blocked on each other: three teams in one repository, each waiting for the others' change to pass review before the pipeline can run, so that the ticketing team's Tuesday fix ships on Thursday because the reporting team's migration was in the way. A domain with its own data and its own load: rendering touches orders, tickets and seats read-only, writes nothing anyone else reads, and needs 8 cores of CPU for 4-second jobs where the rest of the service needs a loop for 8-millisecond ones. The primary's write throughput as the ceiling after everything else is done: the pool sized, the cache in front of every read that can take one, the indexes right, the transactions short, and the on-sale's holds still queueing on one writer's disk. And a second product that needs the same identity: the same users, the same login, the same tokens, serving something that is not ticketing.

Each is a pressure the reader can measure. Deploys blocked is a number of hours a week. A domain's separate load profile is two lines on the dashboard of Chapter 13 that never move together. The primary's ceiling is the write latency on the night, after the postmortem of Topic 75 has found nothing else. What is not a signal is "microservices are what serious companies do," and the test for whether a proposed split is that sentence in disguise is to ask which of the four numbers it changes; a split that changes none of them is fashion, and fashion is the most expensive reason of all.

The First Split Is Not the Hard Part

When the render pipeline becomes its own service, almost nothing changes, and that is the point of doing it first. It gets its own repository, its own deploy, its own container with the 2.5 GB memory limit and the 8-thread pool that Chapter 11 sized for it, and its own on-call. It consumes the render_tickets entries that the outbox relay of Chapter 7 already publishes to the stream, renders the PDFs, writes them to object storage, and sends the email. The API never calls it. There is no request from the checkout handler to the render service, no timeout to set, no breaker to add, because the contract between them was already a stream entry with at-least-once delivery, and the render handler was already written to run twice without sending two emails. The outbox, at-least-once delivery and idempotent handlers are exactly what makes this split safe, and the book spent Chapters 7 and 8 on them for a service that had not split yet.

Stagedoor with the render service beside it: the same stream, a second consumer, no call between them
stagedoor.example · two services, one of them still the book's
Stagedoorapi-01 · api-02worker-01pg-primary
Betweenredis-01: the streamobject storage
Render servicerender-01the render handler

Read the diagram for what is missing. There is no arrow from the API to the render service. The only arrow into it is the stream, and the only arrows out of it go to object storage and the email provider. The render service reads orders and tickets from the same database for now, which is the shortcut the next section prices, and the plan for it is a read-only replica of the two tables published as an event, not a shared connection string forever. This split cost a repository, a pipeline and a week, and it changed nothing about how a checkout works, because the checkout was already designed as if the render were somewhere else.

The Hard Part Is the Data

Two services that share a database are one service with two deploys. They share the schema, so a migration in one breaks the other; they share the primary's write throughput, so the ceiling that motivated the split is untouched; they share the transaction boundary, so either can hold a lock the other waits on, and neither can tell from its own code. Every problem of the monolith remains and a network has been added between the halves. It is the most common first split and the one the book names as a mistake, because it feels like progress and measures as none.

Two services with two databases are separate, and the price is precise. They cannot join: the organizer report that reads events with orders and tickets in one statement becomes two calls and a merge in memory, with pagination on each side. They cannot transact together: the hold that changed a seat and inserted a hold row in one commit becomes, if seats and orders live in different services, the saga of Chapter 10 on every checkout, with a compensation for every step and a reconciliation for every compensation that fails. And each must own its data, with an API between them that is a contract of Chapter 10's kind, versioned and tested with a fake. The seat and the order in different services is the oversell reintroduced as a distributed race: two buyers, two services, no row lock that spans both, and the unique constraint that was the last line of defence in Chapter 6 is now on the wrong side of a network call. That is the cost the reader should price before any split, and the price is paid in every checkout forever, not once.

What Middleware Deep Dive Adds

Every mechanism this book placed inside one process, Middleware Deep Dive places between processes, and the questions it answers are the ones this book declined at the line Chapter 1 drew. The broker chosen for the job: one Redis stream with one consumer group was enough for one service's jobs, and it is not the bus for ten services with different retention, ordering and replay needs, which is the choice between a queue, a log and a stream that the neighbouring course opens with. Delivery guarantees inside the broker, where this book took at-least-once as given and wrote handlers to survive it. The API gateway that does the rate limiting of Topic 74 and the authentication of Chapter 5 once, at the edge, for all services, so that each service does not carry its own copy of the rings. The service mesh that does the mTLS of Chapter 5 and the retries and timeouts of Chapter 7 outside the process, in a sidecar with a configuration file, for calls between services that this book never had to make.

The reader arrives there with a specific advantage: every one of those mechanisms is one they have built inside a process and watched fail. A gateway's rate limit is Topic 74's bucket with the key chosen by someone else; a mesh's retry policy is Topic 38's classification with the predicate in YAML; a broker's consumer group is Chapter 8's with the guarantees printed on the box. Knowing what the mechanism does from the inside is what makes it possible to read the box honestly, and the boxes are not always honest.

What System Design Adds

The questions this book deferred at Chapter 1 are System Design's opening chapters, and they arrive in the order the signals do. Sharding the orders across several primaries when one writer is the ceiling, with the seat and its order on the same shard so the transaction still holds, and the cross-shard report becoming a scatter and a gather. Consistent hashing for a cache that outgrows one Redis, so that adding a node moves a fraction of the keys and not all of them. Multi-region for the buyers on the other coast, where the 80 milliseconds of light between regions is a fact the seat-hold path cannot await and the question of which region owns a seat has no cheap answer. CAP as the vocabulary for what a split gives up: under a partition, a system answers or it stays consistent, and the seat map and the hold path want different answers to that question. And the decomposition of a domain into services with boundaries that survive, which is the discipline of drawing the line where the data already divides, as the render service did, and not where the org chart does.

This book's contribution to that course is the vocabulary and the reflexes. A reader who has built the outbox knows what an event is before it is called one. A reader who has written a saga knows what a distributed transaction costs before the word appears. A reader who has watched the pool arithmetic break at four hosts knows what a shared dependency's ceiling looks like on a graph. System Design asks those questions at a scale of many services; the answers are recognizable because they were met here at a scale of one.

The handoff map, closed: which course the next question belongs to
The signals say split. What do I take with me?The outbox, the key, the saga, the reconciliation: this book
Which broker, which guarantees, a gateway, a mesh?Middleware Deep Dive
Shard the orders, hash the cache, a second region, CAP?System Design
The primary's write ceiling itself, before sharding?PostgreSQL Deep Dive, Chapter 13
The render service's container, its rollout, its probes?Docker · Kubernetes Deep Dive

The Warning

Every mechanism in this book was hard enough with one service. The oversell, the double charge and the late emails all happened inside one process with one database, each was one line of code that was correct on its own, and each took a chapter to fix properly: a row lock and a constraint, a key and an outbox, a queue and an age gauge. Across two services each is harder by the network fact of Chapter 1, squared: the answer can be lost on the way from the buyer to the service and again on the way from the service to its neighbour, and a retry at either boundary can repeat work at both. The oversell across two services is a distributed race with no lock. The double charge across two services is an idempotency key that must be honoured on both sides of a call that can time out in the middle. The late emails across two services is a queue age that lives in another team's dashboard.

So: split when the signals say so and not before, price the data boundary first because it is the only cost that is paid on every request forever, and carry the outbox, the idempotency key, the saga and the reconciliation to the other side, because they are the parts of this book that work there. Everything else, the pool, the loop, the rings, the profile, is about one process and comes along unchanged. The four that cross the network are the four that were built for a network that fails between any two lines of your code, and on the other side of a split there are many more lines and one more network. That fact was the book's first sentence, and it is the last.

Common Mistakes
  • Splitting by fashion — three services, one database, every coupling of the monolith kept and every cost of the network added, and none of the four signals changed by a single number.
  • Splitting the transaction boundary — the seat in one service and the order in another, and the oversell of Chapter 6 reintroduced as a distributed race that no row lock can settle and every checkout must run as a saga.
  • Assuming the mechanisms scale for free — the retry policy that was one client wrapper in Chapter 7 is now the mesh's configuration, the gateway's and the client's, three copies that must agree and a retry storm when they do not.
  • Sharing the database "just for now" — the render service reading orders through the same connection string a year later, with a migration in one repository breaking the other at 3 a.m.
  • Reading this topic as a recommendation — it is a map of the costs, and the recommendation is the sentence before it: one service, built properly, until a signal with a number says otherwise.
Best Practices
  • Name the signal before the split, as a number that the split will change, and reject "serious companies do this" as a signal.
  • Split the thing with its own data and its own cadence first, the render pipeline, along the stream that already exists and with no call from the API into it.
  • Price the data boundary before any other cost: list the joins and the transactions that cross it, and turn each into a call, a saga or a copy before the split, not after.
  • Take the outbox, the idempotency key, the saga and the reconciliation to the other side, and expect each to be needed on every boundary the split creates.
  • Read Middleware Deep Dive for what goes between the services and System Design for how many of them compose, with this book's vocabulary as the entry ticket.
RelatedMiddleware Deep Dive the broker, the gateway and the mesh: what goes between servicesSystem Design sharding, consistent hashing, multi-region, CAP and decomposition: how many services composeMonolith First (Fowler) and the Modular Monolith pattern, the written positions this book agrees withPostgreSQL Deep Dive Chapter 13, the primary's ceiling before any shard

Knowledge Check

Which of these is a signal the book accepts as a reason to split Stagedoor?

  • A domain whose data nobody else writes and whose load profile differs from the rest
  • The observation that companies at Stagedoor's ambition run many services, not one
  • A codebase that has grown past 100,000 lines and takes a new engineer a month to learn
  • A test suite that takes 20 minutes to run, since two smaller suites would each finish sooner

Why is the render pipeline the safe first split?

  • Because the API calls it over HTTP with the timeout and breaker of Chapter 7 already in place
  • Because it needs no data at all from the rest of Stagedoor and so has nothing to share
  • Because rendering is cheap enough that a second copy of it costs almost nothing to run
  • Because it already consumes an outbox-published stream entry idempotently, and the API never calls it

Two teams split Stagedoor into a ticketing service and a reporting service that share pg-primary. What has changed?

  • The write ceiling doubled, because two services can each use the primary's full write throughput
  • The transaction boundary is now clean, because each service commits only its own rows
  • Nothing that mattered: they are one service with two deploys, plus a network between the halves
  • Deploys are now independent, because each repository owns its own migrations and schema

A reader wants to know whether Stagedoor's stream should become Kafka, and separately how to shard orders across two primaries. Which course owns each?

  • This book for the broker choice; PostgreSQL Deep Dive for the sharding, since it is about the engine
  • System Design for the broker choice; Middleware Deep Dive for the sharding of the orders
  • This book for both, since Chapter 8 built the stream and Chapter 6 built the data layer
  • Middleware Deep Dive for the broker choice; System Design for sharding the orders

What is the warning of the last topic, in one sentence?

  • Never split a service, because every problem of many services is worse than any problem of one
  • Split on a signal, price the data boundary first, and take the four mechanisms that cross a network
  • Once the reader knows the vocabulary of the next two courses, the split is safe to make at any time
  • Split the transaction boundary first, because the seat and the order are the busiest tables

You got correct