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.
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:
| Team | Type | Owns | Release rhythm |
|---|---|---|---|
| Returns | stream-aligned | return journey service, eligibility policy, returns UI | daily, own pipeline |
| Payments | stream-aligned | refund settlement, the money ledger, chargeback handling | weekly, money-path change review |
| Platform | platform | build pipeline, runtime, event bus, identity and authorization library | continuous, no product roadmap |
| Storefront monolith | shared codebase | orders, customers, catalogue — the order-of-record | one 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.
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:
- Code and contracts
RefundRequestedcommand (HTTP),RefundSettledevent (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-supportfor 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.
| Pair | Mode | Runs | Converts when |
|---|---|---|---|
| Returns × Payments | collaboration | weeks 1–4 | RefundSettled v1 is published with a consumer-driven contract test in the Returns suite. |
| Returns × Payments | X-as-a-service | week 5 onward | — (steady state) |
| Returns × Platform | collaboration | weeks 1–6 | The carrier-adapter deployment pattern runs from Platform’s self-service template with no Platform commits. |
| Returns × Platform | X-as-a-service | week 7 onward | — (steady state) |
| Platform → Returns | facilitating | weeks 6–8 | Returns 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 | Producer | Consumers | Compatibility policy | Test lives in |
|---|---|---|---|---|
RefundSettled v1 (event) | Payments | Returns, Support console | additive free; breaking = 30d notice + 90d dual-shape window | each consumer’s suite |
OrderLookup v3 (HTTP) | Storefront monolith | Returns, Payments, Fraud | N−1 served for 90 days; deprecation header on the old version | the monolith’s consumer-driven suite |
ReturnResolved v1 (event) | Returns | Analytics, Support console | additive free; Returns owns the schema outright | each consumer’s suite |
returns_* tables | Returns | none permitted | private; a fitness function fails the build on any cross-schema join | the 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)
| Day | Producer does | Consumers do | Reversible? |
|---|---|---|---|
| 0 | Adds order_ref (string) alongside order_id; both populated on every response. | Nothing yet. Nothing breaks. | yes — drop the new field |
| 0 | Announces deprecation of order_id: notice period 30 days, window closes day 120. | Each consumer opens a migration ticket with a named owner. | yes |
| 1–90 | Serves 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 |
| 90 | Checks the counter. Any consumer still reading order_id gets a direct, named escalation — not a broadcast. | Last stragglers migrate. | yes |
| 120 | Stops populating order_id (field still present, null). | Nothing; already migrated. | yes — repopulate the field |
| 150 | Removes 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:
- Anyone may make a decision in the area they own — there is no approver.
- Before deciding, they must seek advice from everyone materially affected and from anyone with genuine expertise. Seeking advice is mandatory; taking it is not.
- The decision is recorded as an ADR, including who advised, what they said, and where the decider disagreed with them.
- 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.
| You see this | What it costs | The 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. |
- “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.
- One team, one deploy train. Skip the compatibility windows. Coupling is the cheap currency when both sides change together; a 90-day window between two modules you deploy in the same commit buys nothing and costs a migration ritual.
- The boundary is temporary. If the returns service will be folded back into the monolith after the pilot, do not spend four weeks hardening a contract across it. Write the seam, note the intent in an ADR, and keep the option.
- Fewer than about four teams and low change rates. A weekly forum where everyone happens to be present is not a bottleneck at that size — the advice process is solving a queue you do not yet have. Adopt it when the queue is measurable.
- The disagreement is about goals, not interfaces. If Payments and Returns disagree about who is accountable for refund speed, no contract register will resolve it. That is an escalation with a concrete decision request (supplement 3), not an architecture exercise.
- An acute incident is in progress. During an incident, collapse the boundaries: one channel, one commander, whoever can fix it fixes it. Restore the process afterwards, and treat any boundary the incident had to cross as design evidence.
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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?
- What is the longest-running “temporary” collaboration between two of your teams, and what would have to be true for it to end?
- 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?
- 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
- Team Topologies, Skelton & Pais — ch. 5 on the four team types, ch. 7 on the three interaction modes, and the team API in ch. 4. The key concepts page is the free summary.
- “Conway’s Law”, Martin Fowler — the short version, including the inverse manoeuvre and why it is expensive.
- “Scaling the Practice of Architecture, Conversationally”, Andrew Harmel-Law — the advice process, the architecture advice forum, and ADRs as the durable record.
- “Published Interface”, Martin Fowler — the distinction between public and published, which is exactly the difference between a module boundary and a cross-team contract.
- The Principles of Product Development Flow, Reinertsen — ch. 3 and 6: queueing as the reason a review board slows down superlinearly rather than linearly.
- Domain-Driven Design, Evans — part IV on context maps: customer/supplier, conformist, anti-corruption layer and separate ways as named relationships between teams.
- Course: lesson 0033 (which currency you pay in), lesson 0046 (ADRs), lesson 0048 (inside one team). Reference: context map, tolerant reader, coupling vs coordination.
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.