Software Design Reference Supplements · Multi-team architecture

Supplement · 1 · Multi-team architecture

Architecture Across Three Teams

One team designs modules. Three teams designs an organization, and the organization then designs the modules back. This supplement is about the second loop: making team boundaries, contracts and governance deliberate before Conway’s law makes them for you.

Reading ≈ 22 minPractice ≈ 60–90 min

Mission tie-in: lesson 0033 named Conway’s law as a design input and lesson 0048 ran one team. This page covers what changes when the initiative crosses three of them.

Why this is here

You reach this page at a specific moment. The returns initiative was scoped for one cross-functional team, and then the design met the org chart: settlement truth lives with Payments, the runtime and event bus live with Platform, and the order-of-record still lives inside the Storefront monolith that two of those teams deploy. Suddenly the interesting work is not “what should the module look like” but “who may change this, how do they tell us, and how long do we have to react.”

The symptoms that send a lead here are consistent: a slice sits blocked for a week on another team’s release train; an architecture review board has become the longest queue in the initiative; two teams have quietly duplicated a rule because agreeing on one owner was slower than writing it twice; and someone has proposed a reorganisation as the fix for a design problem.

Knowledge: three teams, one return

The pilot’s working shape — the concrete case for everything below:

Teams on the returns initiative
TeamTypeOwnsRelease rhythm
Returnsstream-alignedreturn journey service, eligibility policy, returns UIdaily, own pipeline
Paymentsstream-alignedrefund settlement, the money ledger, chargeback handlingweekly, money-path change review
Platformplatformbuild pipeline, runtime, event bus, identity and authorization librarycontinuous, no product roadmap
Storefront monolithshared codebaseorders, customers, catalogue — the order-of-recordone train, two teams commit to it

Conway’s law is not a warning, it is a measurement. The system will acquire a boundary wherever two groups must negotiate to change something, and it will acquire tight coupling wherever one group can change both sides without asking. Read the table above and the architecture is already half-decided: there will be a seam between returns and settlement, because those are different teams with different release rhythms; and there will not naturally be a seam inside the Storefront monolith, because one deploy train covers all of it.

The lead’s job is to make each of those inherited boundaries either deliberate or gone — not to accept them as scenery.

Platform boundaries versus stream-aligned boundaries

A stream-aligned team owns a slice of customer value end to end and is measured on outcome. A platform team owns capabilities that reduce other teams’ cognitive load, and is measured on adoption and on how little of its users’ attention it consumes. The failure mode is not having a platform team; it is a platform team that starts absorbing business rules.

Concretely: when the eligibility rule “refuse returns on items marked final-sale” is proposed as a Platform change — because Platform owns the rules engine — the boundary has just moved in the wrong direction. Platform should own the capability (a rules engine, a deployment path, an audit trail) and never the policy (which items are eligible). The test: if the change would need a product decision, it belongs to a stream-aligned team.

The principle behind the principle

Every cross-team dependency converts a design decision into a queue. A queue has a wait time, and wait time is not paid by the team that owns the queue — it is paid by the team waiting, which is the team accountable for the outcome. So the placement rule is economic, not aesthetic: put the decision on the side of the boundary where the cost of being wrong is felt. Platform owning eligibility policy means the team that suffers the consequence of a bad rule cannot change it, and the team that can change it suffers nothing. Every incentive then pushes towards duplication, because writing the rule twice is cheaper than waiting for someone else’s sprint.

The team API

A team API is the answer to “how does another team use us without booking a meeting?” It is not a document about culture. It is six concrete things, published where consumers look:

Team API · Payments, as published to the returns initiative
Code and contracts
RefundRequested command (HTTP), RefundSettled event (bus). No direct database access; the ledger tables are private.
Versioning
Additive changes any time. Breaking changes: 30 days’ notice, then a 90-day window in which both shapes are served.
How to ask
A pull request against the contract repo for schema changes; #payments-support for questions; an ADR for anything that changes ownership.
Response expectation
Contract PR reviewed within 3 working days. Production incidents: paged. Feature requests: reviewed at the fortnightly intake, not on demand.
On-call and hours
24/7 for settlement failure; business hours for everything else, Central European time.
Roadmap visibility
The next two months of committed work is public, including the freeze around quarter-end close.

Notice what the last two rows do. They convert two invisible facts — that Payments cannot help you at 4 p.m. on the last day of the quarter, and that a feature request has a fortnightly cadence — into planning inputs. Most cross-team surprises are not disagreements; they are undeclared calendars.

Interaction modes are temporary by default

Team Topologies gives three interaction modes: collaboration (two teams working closely, high bandwidth, high cost), X-as-a-service (one consumes what the other provides, low bandwidth, low cost), and facilitating (one team helps another become capable). The mistake is not choosing the wrong one; it is choosing one and never revisiting it. Collaboration is the expensive mode — it should be a project with an end date and an explicit conversion criterion.

Interaction plan · returns pilot, weeks 1–16
PairModeRunsConverts when
Returns × Paymentscollaborationweeks 1–4RefundSettled v1 is published with a consumer-driven contract test in the Returns suite.
Returns × PaymentsX-as-a-serviceweek 5 onward— (steady state)
Returns × Platformcollaborationweeks 1–6The carrier-adapter deployment pattern runs from Platform’s self-service template with no Platform commits.
Returns × PlatformX-as-a-serviceweek 7 onward— (steady state)
Platform → Returnsfacilitatingweeks 6–8Returns can add and run its own architecture fitness functions unaided.

Two things make this table honest rather than decorative. First, every collaboration row has a week number attached, so “we are still collaborating in week 14” becomes a visible fact rather than a mood. Second, every conversion criterion is something you can observe — a published contract, a template with no commits, a fitness function the other team wrote. “Once the teams feel confident” is not a criterion.

Cross-team contracts, compatibility windows, ownership

A contract between teams needs an owner, a compatibility policy with real durations, and a test that fails in the consumer’s build. Without the third, the policy is a chat message. Here is the register the returns initiative maintains — four rows, and the fourth is the interesting one:

Contract register · returns pilot
ContractProducerConsumersCompatibility policyTest lives in
RefundSettled v1 (event)PaymentsReturns, Support consoleadditive free; breaking = 30d notice + 90d dual-shape windoweach consumer’s suite
OrderLookup v3 (HTTP)Storefront monolithReturns, Payments, FraudN−1 served for 90 days; deprecation header on the old versionthe monolith’s consumer-driven suite
ReturnResolved v1 (event)ReturnsAnalytics, Support consoleadditive free; Returns owns the schema outrighteach consumer’s suite
returns_* tablesReturnsnone permittedprivate; a fitness function fails the build on any cross-schema jointhe shared pipeline

The fourth row is a contract that says “there is no contract here.” Declaring a data store private is the cheapest boundary you will ever draw, and the one most often skipped — because nothing forces you to draw it until an analytics query is already joining across it in production.

Now the worked breaking change. OrderLookup must change order_id from a 32-bit integer to an opaque string, because the monolith is being sharded. This is expand/contract run across three teams instead of across one schema:

A compatibility window with dates on it (illustrative)

DayProducer doesConsumers doReversible?
0Adds order_ref (string) alongside order_id; both populated on every response.Nothing yet. Nothing breaks.yes — drop the new field
0Announces deprecation of order_id: notice period 30 days, window closes day 120.Each consumer opens a migration ticket with a named owner.yes
1–90Serves both. Emits a Deprecation response header and counts reads of the old field per consumer.Migrate to order_ref; add a contract test asserting the new field is read.yes
90Checks the counter. Any consumer still reading order_id gets a direct, named escalation — not a broadcast.Last stragglers migrate.yes
120Stops populating order_id (field still present, null).Nothing; already migrated.yes — repopulate the field
150Removes the field from the schema.—no

Five months from announcement to removal, and only the last step is irreversible. That looks slow until you price the alternative: a synchronised deploy across three teams with different release rhythms — daily, weekly, and a monolith train — whose earliest common window is the monolith’s next train, and whose rollback story is “all three roll back together, at 2 a.m.”

The measurable part is the counter. Without per-consumer read counts on the deprecated field, day 90 is a guess and day 150 is a gamble. Instrumenting the old field is what converts a compatibility policy into a compatibility window.

Governance without a review board bottleneck

Three teams need some architectural coherence, and the reflex is a review board: a weekly meeting where designs are approved. It fails predictably, and the failure is structural rather than cultural. A board is a single-server queue serving several producers; as the number of teams grows, the wait grows faster than the number of decisions, and teams respond rationally by shrinking decisions until they no longer need approval. You end up with a slow queue and unreviewed architecture.

The alternative is the advice process, made durable by ADRs. Its rules are short:

  1. Anyone may make a decision in the area they own — there is no approver.
  2. Before deciding, they must seek advice from everyone materially affected and from anyone with genuine expertise. Seeking advice is mandatory; taking it is not.
  3. The decision is recorded as an ADR, including who advised, what they said, and where the decider disagreed with them.
  4. A regular forum reviews recorded decisions — to spread context and spot conflicts, not to approve. It sits after the decision, not in front of it.

The third rule is the load-bearing one. Recording rejected advice is what makes the process safe to run without an approver: a decision that overrode two teams’ objections is visible, attributable, and easy to revisit when it turns out badly. Boards produce a decision with no dissent recorded, which is exactly the artifact that is useless in eighteen months.

Scale the ceremony to reversibility, as lesson 0046 argues. Changing a retry policy inside one service: decide, note it in the service’s log, move on. Changing who owns settlement truth: an ADR, advice from Payments and Fraud in writing, and a named decider.

Field notes · organizational coupling, and what it costs
You see thisWhat it costsThe move
A slice blocked on another team’s release train.Wait time paid by the team accountable for the outcome.Version the contract so the sides deploy independently.
The same eligibility rule in two codebases.Divergence: one of them will be quietly wrong.Give it one owner; the other consumes a contract.
A platform team writing business rules.The team that suffers the bad rule cannot change it.Platform exposes capability; the stream team owns policy.
Collaboration mode still running in week 14.Two teams’ full attention, priced as if free.Name the conversion criterion; convert or admit it is one team.
An architecture board with a two-week queue.Decisions shrink until they escape review entirely.Advice process plus ADRs; the forum reviews, it does not approve.
Another team joining on your returns_* tables.A contract you did not write and cannot version.Declare the store private; enforce with a fitness function.
What people get wrong after reading this
“Conway’s law means we should reorganise the teams to match the architecture we want.”
The inverse manoeuvre is real, but it is the most expensive tool in the box and it resets every team’s context. Try the cheap moves first: move one component’s ownership, publish a contract, convert an interaction mode. Reorganise when the boundary you need is one no contract can produce.
“Microservices give teams independence.”
Deployment independence, yes. But splitting a system without splitting the decisions just converts coupling into coordination — and coordination is the currency you pay forever (lesson 0033). Two services with a synchronous, unversioned call between them across a team boundary are more coupled than one module was.
“A platform team is a team that owns shared code.”
Shared code with no product is a library, and it needs one owner and a version. A platform team is defined by having users who could choose not to use it. If teams are mandated onto your platform, you lose the only feedback signal that tells you whether it is any good.
“Our contracts are documented in the wiki.”
A contract that no build can fail is a description, not a contract. The test that matters lives in the consumer’s suite and runs against the producer’s published shape; anything else discovers the breach in production.

When this is the wrong move

Most of this page is overhead for a system with one team, and applying it early is a real cost, not a harmless precaution.

Skill: place the boundary

A platform team is asked for one team’s business rule. It should:

The 90-day compatibility window on OrderLookup exists so that:

Under an advice process, an architecture decision is made by:

Practice: map your own organization

Do this on the system you are actually responsible for, not on the returns case.

  1. Draw the real boundaries. List every component in your initiative and the team that can merge a change to it without asking. Where two teams appear on one component, you have a shared codebase — mark it, because it will behave like one team whether you want it to or not.
  2. Price one wait. Pick the cross-team dependency that blocked you most recently. Measure the actual wait in days from request to unblocked. Multiply by the cost of delay of the slice it blocked (supplement 2 shows how to derive that figure). That number is the budget for fixing the boundary.
  3. Write one team API — yours. All six rows: contracts, versioning, how to ask, response expectation, on-call and hours, roadmap visibility. Publish it where consumers already look. The rows you find hard to write are the facts your consumers are currently guessing at.
  4. Build the contract register. One row per cross-team contract: producer, consumers, compatibility policy with real durations, and where the test lives. Add a row for every data store that should be private but is not.
  5. Date your interaction modes. For each pair of teams, name the current mode, the date it started, and the observable criterion that converts it. Anything in collaboration mode with no end date is a merger nobody has announced.
  6. Time your governance. For the last five architecture decisions, record the days from “we knew we had to decide” to “decided.” If the median exceeds a week and the reason is waiting for a forum, you have a queue rather than a governance process.
Teach it back
  1. Which boundary in your system exists because of a real design force, and which exists only because two groups happen to report to different people?
  2. What is the longest-running “temporary” collaboration between two of your teams, and what would have to be true for it to end?
  3. Name a decision you made in the last month that affected another team. Who did you seek advice from, and where is that recorded? If the answer is “nowhere,” who will have to reconstruct your reasoning, and when?
  4. If your team’s data store were declared private tomorrow, which query somewhere else in the company would break — and would you have found out before or after it broke?
Reveal: the two-question boundary test

For any proposed boundary, ask: (1) can each side change and deploy without the other? If not, it is not a boundary, it is a seam with extra ceremony — and you are paying coordination cost for coupling you still have. (2) When the contract must break, who pays, and do they know the price in advance? If the answer is “we would sort it out at the time,” the compatibility policy does not exist yet and the boundary is only decorative.

A boundary that passes both is worth its cost. One that passes neither should be deleted and the two sides merged into one deploy unit — which is usually a cheaper, more honest architecture than the distributed version.

Your win

You can read an org chart as a partial architecture, place capability and policy on the correct sides of a team boundary, publish a team API and a contract register with real durations, run a breaking change across three teams without a synchronised deploy, and govern architecture with recorded decisions instead of a queue.

Read deeper

Ask your agent-teacher to play each of the other two teams in turn and object to your contract register — then check whether your compatibility policy survives their objection or merely outranks it.