Software Design Reference Master Designer · System Trade-Offs

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:

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.
Field notes · what it looks like in real code
SignalThe currency you are payingThe 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 servicesCoupling through the schema, invisible in the code.One owner; the other reads a projection or an event.
the API rules live in a Slack threadCoordination debt: person-dependent and undiscoverable.A compatibility document plus a contract test.
one team blocked on another’s releaseCoupling 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

Map your boundaries and teams with your agent-teacher and find the undocumented contracts.