Lesson 0033 · Master Designer · Module 2
Coupling vs Coordination
Decouple two systems and they stop breaking each other — but someone must keep them aligned. Conway's law says the alignment cost follows team boundaries. This is the trade where people are a force.
Mission tie-in: the third System Trade-Off — where architecture stops being code-only and starts being about teams.
Knowledge: the two costs
Coupling — two components share a contract: an interface, a schema, a synchronous call. When one changes, the other must change with it. Cost: change amplification (lesson 0006) — a deploy on one side is a coordinated deploy on both.
Coordination — two components are independent, but someone must keep them aligned: versioned contracts, migration windows, compatibility rules, message schemas with both-ways guarantees. Cost: an ongoing conversation that never ends.
The two forces are change amplification — one edit forces a second edit somewhere you do not own — and alignment cost — the standing human work of keeping two independent things agreeing. The unit of decision is the boundary: one pair of parts, one contract between them.
The mechanism is that information has to cross the boundary somehow, and there are only two carriers. Either the agreement is compiled into both sides (a shared type, a schema, a synchronous call), in which case a change is cheap to make and expensive to make separately; or it is maintained by people (versions, compatibility windows, deprecation notices), in which case separation is cheap and each change costs a negotiation. You are choosing which of those two bills you would rather receive, and there is no third option where neither arrives.
The two bills scale differently, which is what actually decides most cases. Coupling cost grows with call sites × change frequency: a stable contract with a hundred callers costs nearly nothing, because the multiplier is zero. Coordination cost grows with the number of parties that must agree — n(n−1)/2 pairs — and it is charged whether or not anything changed, because staying aligned is itself the work. So the rule inverts at two boundaries: where the contract barely changes, coupling is nearly free and coordination machinery is pure overhead; and where both sides sit inside one team with one deploy train, the "coordination" is a sentence at standup, and paying for versioned contracts buys independence nobody will use.
You cannot have neither. Zero coupling with zero coordination is two systems that stopped agreeing. The design question is which currency you pay in:
- Coupling pays when the contract is stable: a shared library for a domain concept used by two services, versioned together, deployed together.
- Coordination pays when the sides evolve at different speeds: an event contract (lesson 0025) where consumers tolerate unknown fields and producers keep compatibility.
Conway's law is the tiebreaker: systems mirror the communication structure of the people who build them. A boundary that separates two teams is a coordination boundary whether you design it or not — design it deliberately. And team topologies adds the human cap: every boundary is cognitive load on the teams that must hold both sides of it in their heads.
Coordination only exists if someone writes it down. Here it is as code both sides can run:
# the published contract — the coordination artifact, versioned
@dataclass(frozen=True)
class OrderConfirmedV1:
order_id: str
confirmed_at: datetime
def parse_order_confirmed(payload: dict) -> OrderConfirmedV1:
return OrderConfirmedV1( # a tolerant reader
order_id=payload["order_id"], # required: removing it breaks us
confirmed_at=datetime.fromisoformat(payload["confirmed_at"]),
) # unknown keys: ignored, on purpose
Three rules turn a schema into a contract: producers may add fields and never remove or repurpose them; consumers ignore fields they don't recognise; both sides support N−1 versions for a stated window. A consumer-driven contract test in each team's suite makes those rules fail the build rather than fail in production.
Written down and tested, that is coordination doing its job. Left in a chat thread — "we agreed the field is optional" — it is coordination debt: person-dependent, undiscoverable, and payable at 3 a.m.
Worked: pricing the boundary instead of arguing about it
Checkout (six engineers) calls warehouse (five engineers) for a stock check, synchronously, against a shared schema with no versioning. Every checkout release waits for a warehouse release: two release trains a month, and each one costs two people roughly three hours of sequencing, verification and Slack — 12 person-hours a month, 144 a year, about three and a half person-weeks burned on a runbook line that says "deploy warehouse first".
The first thing a lead looks at is not the diagram; it is git log on the contract. How often has this schema actually changed? Eleven times in twelve months. That number is the whole decision. At eleven changes a year the coupling multiplier is live and the bill is real, so the machinery pays: a written compatibility contract plus consumer-driven tests costs about three days to set up and two hours per subsequent change — 24 hours once, plus 24 hours a year — against 144 hours a year saved. It breaks even in roughly four months. Had the answer been "changed once, in 2023", the same machinery would have been an expensive way to make a stable thing look modern.
The alternative the lead rejects is the popular one: "split them into proper microservices so the teams are decoupled". Rejected because the coupling is not in the deployment unit — it is in the shared schema and the shared concept. Splitting the process without splitting the data ownership adds a network hop, a retry policy and a new failure mode to exactly the same negotiation. Deployment independence is not change independence, and only the second one was costing 144 hours.
The second rejected alternative is subtler: keep the synchronous call but "just version the endpoint". Versions with no stated support window are names, not commitments. Without "we support N−1 for 90 days" written down and tested, both sides still deploy together — you have added ceremony to the runbook and removed nothing from it.
A year on, the difference is visible in what a new engineer has to know. With the contract written and tested, warehouse can ship a field on a Tuesday and checkout finds out from the test suite, not the incident channel; onboarding reads one document. Without it, the alignment lives in the heads of two long-serving engineers, and the day one of them leaves is the day you discover which rules were never rules at all.
† The compatibility contract is the coordination artifact: a schema/API with documented rules — "consumers must tolerate unknown fields", "producers support N versions back". If no one owns that document, you have coordination debt: silent, person-dependent, and discovered in production.| Signal | The currency you are paying | The move |
|---|---|---|
| a runbook that says “deploy A before B” | Both currencies at once — coupling and coordination. | Version the contract, or merge the two into one deploy. |
| one table written by two services | Coupling through the schema, invisible in the code. | One owner; the other reads a projection or an event. |
| the API rules live in a Slack thread | Coordination debt: person-dependent and undiscoverable. | A compatibility document plus a contract test. |
| one team blocked on another’s release | Coupling drawn straight across a team boundary. | Decouple with events, or move the boundary. |
- "Decoupled means we no longer have to talk."
- Decoupling converts a synchronous conversation into an asynchronous, written one. The talking does not stop; it becomes a document with an owner. Teams that skip the document have not decoupled — they have gone quiet, which is a different and worse thing.
- "Microservices decouple teams."
- They grant deployment independence, which is not change independence. Two services sharing one table, or one concept with contested rules, are as coupled as two modules — with a network in the middle so the coupling now fails at runtime.
- "Conway's law means we have to reorganise the company."
- It means whichever boundary you do not choose deliberately, you get accidentally. Moving a boundary in the code to match how people already communicate is usually far cheaper than moving the people, and it is available to you this sprint.
- "We version our API, so compatibility is handled."
- A version number without a support window, a deprecation date and a test that fails when you break N−1 is a label, not a guarantee. The commitment is the part that costs something; the number is free.
When this is the wrong move
Do not buy coordination machinery for a boundary inside one team. When the same six people own both sides and ship on one train, the versioned event contract, the compatibility window and the consumer-driven test suite are ceremony around a conversation that already happens at standup — and worse, they make the cheap move (change both sides in one commit) feel illegal.
Nor should you freeze a contract early. In the first months of a product the model is wrong in ways you cannot yet name; a compatibility document written then locks in that wrongness and charges you a migration every time you learn something. Couple hard, refactor freely, and write the contract when the concept stops moving — the signal is usually a second consumer you do not control.
In the lab: the companion Phase 3 exercise gives you a returns service whose agreement with the order system exists only as field names copied between two modules. You write the compatibility rules down, add a contract test that fails when a required field disappears, and decide out loud which side pays when the two disagree.
Skill: which currency do you pay?
Two services share one domain concept, one team, one deploy train. Choose:
Two teams evolve at different speeds and deploys are independent. Choose:
Conway's law means boundaries should follow:
Practice on your own code
Map your system's service boundaries onto your team structure. For each boundary: who changes on each side, who must talk when a contract changes, and is that conversation written down anywhere? The gaps you find are coordination debt — write the compatibility contract.
Reveal: a boundary audit
A checkout service and a warehouse service are split across two teams but share a synchronous stock-check call with no versioning. Audit: the deploy coupling was real (checkout deploys blocked on warehouse), and the compatibility rules existed only in a chat thread. Fix: an event contract (StockReserved, tolerance rules documented) + a compatibility document owned by both teams.
- In two sentences, explain to a junior why "we removed the dependency" and "we removed the coordination" are different claims.
- Find one boundary in your own system that crosses a team edge. Count its contract changes in the last year from version control, and multiply by what one coordinated release actually costs in people-hours.
- Predict the one-year state if the rules stay in chat: who is the single person who still knows them, and what happens the week they are on holiday?
Your win
You can choose the payment currency — coupling for stable co-evolving contracts, coordination for independent evolution — and you know Conway's law as a design input, not an excuse.
Read and watch deeper
- Team Topologies, Skelton & Pais — ch. 3–4: cognitive load as the cap on how many boundaries a team can hold, and the four interaction modes.
- "Consumer-Driven Contracts", Robinson on martinfowler.com — the test that turns an agreement into something a build can fail on.
- Conway's law — the original observation, read as a design principle.
- Designing Data-Intensive Applications, Kleppmann — ch. 11: the coordination guarantees (at-least-once, ordering) behind event contracts.
- Watch: ArjanCodes YouTube — search "conway's law" or "team boundaries".
- Next: lesson 0034 — Performance vs Maintainability.
- Reference: Trade-offs — coupling vs coordination; glossary — tolerant reader.
Map your boundaries and teams with your agent-teacher and find the undocumented contracts.