Architecture

Where to Put the Boundary Between Services

Transaction scope, ownership and deploy cadence, not domain diagrams.

Chapter 5 of 1311 min readOpen access

Where to Put the Boundary Between Services is a question about transactions, ownership and deploy cadence, and it is answered badly whenever it is answered from a domain diagram. A boundary drawn through data that must change together does not divide a system; it converts a database guarantee into an application responsibility that somebody will have to implement, test and reconcile.

Key takeaways

  • Draw the boundary where the transaction already stops. Data that must change atomically belongs on one side of the line, and no amount of domain modelling changes that.
  • Helland's CIDR 2007 paper reports that large scalable applications do not assume distributed transactions, and that projects which try founder because the performance costs and fragility make them impractical.
  • A saga is not free coordination. It is one compensating action per step, each of which is new code with its own failure mode and its own test.
  • Conway's 1968 paper states that a system's structure copies its organisation's communication structure, so a boundary that cuts across a communication path will drift back.
  • Deploy cadence is a boundary signal. Two components that always ship together are one component with extra network hops between them.

Read this after Chapter 4, which produced the model whose atomic units this chapter respects, and after Chapter 3, whose vocabulary makes transaction scope discussable. Chapter 8 handles what happens when one cross-boundary call fails, and Chapter 9 handles what happens when everything on the far side of a boundary is gone, so this chapter stays on where the line goes rather than on what happens along it.

A team splits a monolith into services along the entities in its domain model. Orders, inventory, invoicing, notifications. It reviews well and the diagram is clean.

Six weeks in, placing an order requires a call to inventory to reserve stock and a call to invoicing to raise a draft. Either can fail after the other succeeded.

The team writes a reconciliation job. Then a second one, for the cases the first one misses. The diagram never changed. The work did.

A boundary is where a transaction stops

The definition that makes this decision tractable is narrow. A service boundary is the edge of a set of data that the system can change atomically.

Inside the boundary, correctness is the store's problem, expressed in Chapter 3's vocabulary: pick a level, handle the retry, and the invariant holds. Outside it, correctness is your problem, expressed as ordering, retries, idempotency keys and compensations, all of which you write and maintain.

That is the whole cost of a boundary. Everything else about it, the language, the repository, the deployment target, is comparatively cheap and comparatively reversible.

So the design move is to enumerate the invariants first. For each one, ask whether it spans the candidate line. If it does, either the line moves or you are choosing to hold that invariant in application code forever.

The entity is the unit you can change atomically

Helland's CIDR 2007 paper supplies the useful abstraction, and it is more precise than the word aggregate as usually deployed.

The paper distinguishes entities from activities. An entity is a collection of data with a unique key that lives on a single machine at a time, and an application can only manipulate one entity at a time. Activities are the coordination between entities, and the paper's position is that scalable applications must be written with that constraint accepted rather than hidden.

Two consequences follow directly. First, your atomic unit is bounded by the entity, so an invariant across two entities is always going to be maintained by a protocol rather than by a transaction. Second, entity identity is a design artifact worth arguing about, because it decides what can be made atomic later without a migration.

Get the entity right and the boundary is nearly free. Get it wrong and every boundary you draw is expensive.

Distributed transactions are where projects founder

The paper does not hedge on the alternative, which is why it is worth quoting rather than paraphrasing.

Helland writes that large scalable applications do not assume distributed transactions, and that when projects attempt them, they founder because the performance costs and fragility make them impractical. That is a 2007 observation from somebody who had spent a career building transaction systems, and nothing since has overturned it.

The engineering content of that sentence is specific. A two-phase commit across services holds locks for the duration of a network round trip. It converts every participant's availability into a factor of the whole operation's availability. And it leaves an in-doubt state that needs an operator when the coordinator dies at the wrong moment.

So the real choice is not between a distributed transaction and a saga. It is between moving the boundary and accepting a protocol.

A saga is one compensating action per step

Sagas are the standard answer and they are frequently presented as though they cost only a diagram. They cost code.

For each step in the sequence, you write the forward action and a compensating action that undoes its business effect. The compensating action is new code with its own bugs, its own tests and its own idempotency requirement. It also has its own behaviour when it fails, which is the part nobody plans. You need somewhere to record which steps have completed, so the sequence can resume after a crash. That record is state with a durability decision from Chapter 2.

Count the artifacts before agreeing to one. Four steps means four forward actions, four compensations, one persistent sequence record, and a monitoring surface that can show a saga stuck halfway. That is roughly ten things to build and operate in place of one transaction.

Sagas are the right answer sometimes. They are almost never the cheap answer.

The design will copy your communication structure

There is a structural force acting on your boundary regardless of what the design document says, and it was described nearly sixty years ago.

Conway's paper, published in Datamation in April 1968, states the result plainly. Any organisation that designs a system will inevitably produce a design whose structure is a copy of the organisation's communication structure. His worked example is concrete: a team of five assigned to a COBOL compiler and three to an ALGOL compiler produced a five-phase compiler and a three-phase compiler respectively.

Read that as a constraint rather than as a joke about org charts. A boundary that cuts across a path two people use fifty times a day will erode, because the cheapest way to get work done will route around it. A boundary that follows a real ownership line will hold, because nobody has to fight it.

The practical test is short. Name the team that owns each side of the candidate line, and name the person who approves a change to the contract between them. If either answer is the same team, you have drawn a module, not a service.

Deploy cadence is the cheapest boundary signal

There is one signal that is easier to read than any of the above and it is available from your existing repository.

Look at which components have shipped together every time for the last three months. Two things that always deploy in lockstep are one thing with a network hop inserted into the middle of it, and you are paying serialisation, latency and partial-failure cost for a boundary that provides no independence in return.

The inverse is equally useful. A component that has shipped fourteen times while its neighbour shipped twice is a component with its own cadence, its own risk profile and a genuine case for its own boundary.

That signal costs one query against your deployment history. It is more honest than any domain diagram, because it reflects what the organisation actually does rather than what it intended.

Prefer a soft dependency to a hard one

Some cross-boundary calls are unavoidable, and their cost depends on whether the caller fails when the callee does.

The AWS Well-Architected reliability pillar's REL05-BP01 practice frames this as turning applicable hard dependencies into soft ones through graceful degradation. A component should still perform its core function while serving slightly stale, alternate or no data. That is a boundary-placement criterion, not only a runtime tactic. A line you can cross with a soft dependency is a cheap line. A line whose crossing is mandatory for the core function is an expensive one, and it belongs somewhere else.

So test each candidate boundary with one question. If the far side is unavailable for four minutes, can the near side still do its main job in a reduced form. Where the answer is yes, the boundary is affordable. Where it is no, either move the line or accept that you have coupled two availabilities together, which is Chapter 9's subject.

A worked example, composited and labelled

The details here are composited from ordinary production shapes rather than one system, and the shape is exact.

A ticketing product needs one invariant above all others: a seat is sold once. It also needs an invoice, a confirmation email, a loyalty accrual and an analytics event per sale.

The first design put seats and orders in separate services, because they were separate entities in the domain model. That cut the sold-once invariant in half, which produced a reservation protocol, a timeout for abandoned reservations, a compensating release, and a nightly job to find seats that were reserved and never paid for. Four artifacts, one invariant, permanently.

The second design moved the line. Seats and orders became one service holding one transaction, because they share the invariant that matters. Invoicing, email, loyalty and analytics moved outside as soft dependencies, each triggered from a durable outbox, each tolerant of being minutes late, none able to fail a sale.

The constraint recorded alongside it is that a sale is atomic and everything downstream of a sale is eventually consistent within five minutes. The reversal signal is a second invariant appearing that spans the new boundary, which would mean the line is now in the wrong place.

The objection: we need separate services to scale

This objection conflates two different kinds of scaling, and separating them usually resolves it in five minutes.

Scaling throughput does not require a service boundary. A stateless tier scales by copying, as Chapter 2 established, and you can run twenty instances of one deployable as easily as twenty instances of four. What a boundary buys is independent scaling of components with genuinely different resource profiles, which Chapter 1's per-class arithmetic will tell you whether you have.

Scaling an organisation is the real motivation most of the time, and it is legitimate. Conway's result says as much: independent teams need independent deployables. That argument should be made explicitly, in the decision record, as an organisational constraint rather than dressed up as a performance requirement.

The cost of the confusion is a distributed transaction bought to solve a staffing problem. Name the real driver and the boundary usually moves.

Chapter summary

A service boundary is the edge of a set of data the system can change atomically, so correctness inside it is the store's problem and correctness across it becomes ordering, retries, idempotency and compensation that you write and maintain forever. Helland's CIDR 2007 paper gives the abstraction: an entity is data with a unique key that lives on one machine at a time, an application can manipulate only one entity at a time, and large scalable applications do not assume distributed transactions, because projects that try founder on the performance costs and fragility. That makes the real choice moving the boundary or accepting a protocol, and a saga is the protocol with a price tag of one compensating action per step, plus a durable sequence record and a monitoring surface, which is roughly ten artifacts in place of one transaction. Conway's 1968 paper adds a force acting on the line regardless of intent, since a design copies its organisation's communication structure, so a boundary cutting across a busy communication path will erode. Deploy cadence is the cheapest available signal, because components that always ship together are one component with a network hop in it. AWS's REL05-BP01 practice supplies the affordability test, which is whether the near side can still do its core function with the far side unavailable, and the chapter's decision is a boundary recorded with the invariants it does not cut and the new invariant that would move it.

The line is drawn and some calls now cross it. Before the failure behaviour of those calls can be designed, there is one more shared decision to settle, because a cache is the most common thing sitting on a boundary and the least often given a written contract. Chapter 6 is Caching: Correctness First.

Sources

  1. Life beyond Distributed Transactions: an Apostate's OpinionP. Helland, CIDR 2007 · 2007-01 · Research paper · verified
  2. How Do Committees Invent?M. E. Conway, Datamation · 1968-04 · Research paper · verified
  3. REL05-BP01: Implement graceful degradation to transform applicable hard dependencies into soft dependenciesAWS Well-Architected Framework, Reliability Pillar · 2026-07-29 · Official documentation · verified
  4. PostgreSQL 18 documentation: Transaction IsolationPostgreSQL Global Development Group · 2026-07-29 · Official documentation · verified