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.
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.
† 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. |
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.
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 — team cognitive load and the boundaries this lesson weighs.
- 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.