Module 8: Capstone Project — Be Mercado's Architect Through a Change

4. Apply the inverse Conway maneuver

Overview

This is step 3 of the deliverable, and it's where the governing attribute becomes organization. In lesson 3 you derived that scalability is what this change must produce —a sellers surface that scales independently—. But an attribute isn't achieved by drawing it: it's achieved by structuring the teams that produce it. By the end of this lesson you'll have the deliverable's third artifact —the executed org↔architecture map— and you'll have measured, in Python, how much the system's coordination friction drops by applying the inverse Conway maneuver to the sellers surface, without touching a line of code. This is the step that translates "we want the surface to scale" into "we create a team that owns it end to end", which is the only way for that surface to really scale.

This matters because the most expensive mistake when introducing a big change is letting Conway's Law operate against. A change like "open to external sellers" brings a new surface —the Seller API, the onboarding, the listing ingestion, the payouts— that, if no one owns, gets split up by proximity among the existing squads: platform and orders fight over the API, catalog and platform fight over the ingestion, payments and platform fight over the payouts. That surface co-owned by several teams can't scale, because every change demands coordinating two or three squads —exactly the opposite of what scalability asks for—. The inverse maneuver flips the sequence: instead of drawing three independent services and praying the teams respect the boundaries, you design the organization that will produce those services —a stream-aligned team that owns the complete surface— and the independent services come out almost on their own, because Conway's force now pushes in the direction you want. First the organization, then —and almost on its own— the architecture.

Connection with the module: this lesson does step 3 of the thread and contributes the M2 piece to the capstone. It receives its input from lesson 3 (the governing attribute: scalability) and from lesson 2 (the ownership map: the 38 local decisions that get delegated need teams with clear boundaries to absorb them). Its output —the structural decision: create a seller_platform team and turn the platform into a service— is exactly what lesson 5 will communicate with the C4 and the ADR. The module's frontier is respected: here you design the org↔architecture dynamic and measure it; the detailed team restructuring as a management problem (who to hire, how to transfer people) stays out. Here the object is the lever —how moving the organization moves the system—, measured.

To change the river's course, don't push the water

A river comes down a slope and, on reaching the valley, makes a sharp bend that floods a field every rainy season. You want the water to go straight. You have two ways to try it.

The first, the naive one: push the water. You place sandbags, divert the flow by hand, dig a little channel to guide the current. It works for a few days. As soon as it rains hard, the water returns to its usual bend, throws down the sandbags, and re-floods the field. You fight against the water every season, forever, and you always lose —because the water doesn't follow your sandbags, it follows the shape of the terrain—.

The second, the one that works: change the terrain. Instead of pushing the water, you dig a new, straight channel and block the old one with an embankment. Now the water goes straight not because you're pushing it, but because the terrain carries it there naturally. You don't have to do anything each season; the river flows straight on its own. One big job once, instead of a small fight forever.

The code is the water; the organization is the terrain. Putting the sellers surface into the system without giving it an owning team —letting catalog, orders, payments, and platform split it up— is pushing the water: even if you draw "the Seller API is an independent service", the code will re-couple with everything, because the four teams that touch it will keep sharing and coordinating, and the surface will reflect that communication, not your diagram. The inverse Conway maneuver is changing the terrain: you create a team that owns the complete surface (you dig the new channel) and the surface flows toward an independent service, on its own, because now the communication structure carries it there. This lesson measures how much changing the terrain straightens the river.

Worked example: the inverse maneuver, measured on the change

We're going to model Mercado's system with the four new modules the change brings (seller_api, seller_onboarding, listing_ingestion, payout_processing) and measure the coordination friction of two organizations: the trap (the surface split up by proximity, with no owner) and the inverse maneuver (a stream-aligned team owning the complete surface, plus the platform as a service). A module's friction is C(k,2) —how many pairs of teams have to coordinate over it—; a dependency toward a platform service doesn't put that team in the room, because it's consumed via a stable contract, not by co-change.

# Capstone step 3: the INVERSE Conway maneuver applied to the change.
# To OBTAIN the architecture the change needs (an external sellers surface,
# independent and scalable), we design the ORGANIZATION that would produce it,
# and we measure the coordination friction before/after -- without touching the code first.
from itertools import combinations

# Modules of the system, including the 4 NEW ones the change brings (seller surface).
deps = {
    "search":             ["product_catalog"],
    "cart":               ["product_catalog"],
    "checkout":           ["cart", "payment_processing", "shipping_labels",
                           "notifications", "order_processing"],
    "order_processing":   ["shipping_labels", "auth"],
    "payment_processing": ["invoicing", "auth"],
    "invoicing":          ["notifications"],
    "shipping_labels":    ["delivery_tracking"],
    # --- new external sellers surface ---
    "seller_api":         ["seller_onboarding", "listing_ingestion",
                           "payout_processing", "auth"],
    "seller_onboarding":  ["auth", "notifications"],
    "listing_ingestion":  ["product_catalog", "search"],
    "payout_processing":  ["payment_processing", "invoicing"],
}
modules = ["product_catalog", "search", "cart", "order_processing", "checkout",
           "payment_processing", "invoicing", "shipping_labels", "delivery_tracking",
           "auth", "notifications",
           "seller_api", "seller_onboarding", "listing_ingestion", "payout_processing"]


def total_friction(owners, as_a_service):
    """Sum of C(k,2) over all modules: how many pairs of teams have to coordinate
    per module. A dependency toward a platform service (as_a_service) does NOT put
    that team in the room: it's consumed via a stable contract."""
    total = 0
    for m in modules:
        room = set(owners[m])
        for d in deps.get(m, []):
            if d not in as_a_service:
                room |= owners[d]
        total += len(list(combinations(room, 2)))
    return total


# BEFORE (the trap): the new surface arrives with NO clear owner and is split up by
# proximity among the existing squads -> co-ownership and co-change everywhere.
before_owners = {
    "product_catalog": {"catalog"}, "search": {"catalog"},
    "cart": {"orders"}, "order_processing": {"orders"},
    "checkout": {"orders", "payments"},
    "payment_processing": {"payments"}, "invoicing": {"payments"},
    "shipping_labels": {"shipping"}, "delivery_tracking": {"shipping"},
    "auth": {"platform"}, "notifications": {"platform"},
    "seller_api":        {"platform", "orders"},   # no one owns it: two squads split it up
    "seller_onboarding": {"platform", "orders"},
    "listing_ingestion": {"catalog", "platform"},
    "payout_processing": {"payments", "platform"},
}
before_service = set()   # nothing is a service: everything is co-changed

# AFTER (inverse maneuver): a stream-aligned team 'seller_platform' that owns the
# COMPLETE sellers surface (a single owner); and auth + notifications + payment
# become platform services with a stable contract (consumed, not co-changed).
after_owners = {
    "product_catalog": {"catalog"}, "search": {"catalog"},
    "cart": {"orders"}, "order_processing": {"orders"},
    "checkout": {"orders", "payments"},
    "payment_processing": {"payments"}, "invoicing": {"payments"},
    "shipping_labels": {"shipping"}, "delivery_tracking": {"shipping"},
    "auth": {"platform"}, "notifications": {"platform"},
    "seller_api":        {"seller_platform"},
    "seller_onboarding": {"seller_platform"},
    "listing_ingestion": {"seller_platform"},
    "payout_processing": {"seller_platform"},
}
after_service = {"auth", "notifications", "payment_processing"}

fb = total_friction(before_owners, before_service)
fa = total_friction(after_owners, after_service)


def seller_api_pairs(owners, as_a_service):
    room = set(owners["seller_api"])
    for d in deps["seller_api"]:
        if d not in as_a_service:
            room |= owners[d]
    return len(list(combinations(room, 2)))


print(f"{'scenario':<40}{'#owners seller_api':>19}{'total friction':>16}")
print(f"{'BEFORE (surface split up, no owner)':<40}"
      f"{len(before_owners['seller_api']):>19}{fb:>16}")
print(f"{'AFTER (inverse maneuver: seller_platform)':<40}"
      f"{len(after_owners['seller_api']):>19}{fa:>16}")
print()
print(f"seller_api friction : {seller_api_pairs(before_owners, before_service)}"
      f" -> {seller_api_pairs(after_owners, after_service)} pairs")
print(f"total friction      : {fb} -> {fa}"
      f"  (down {(1 - fa / fb) * 100:.0f}%)")

# Communication paths: the new team keeps its size under control.
def paths(n):
    return n * (n - 1) // 2

print()
print(f"seller_platform of 6 people -> {paths(6)} internal channels (small, fast).")
print("We don't touch the code first: we create the TEAM that will produce the architecture.")

What to expect. Running it:

scenario                                 #owners seller_api  total friction
BEFORE (surface split up, no owner)                       2              21
AFTER (inverse maneuver: seller_platform)                  1               7

seller_api friction : 6 -> 0 pairs
total friction      : 21 -> 7  (down 67%)

seller_platform of 6 people -> 15 internal channels (small, fast).
We don't touch the code first: we create the TEAM that will produce the architecture.

Pause on the numbers, because they're the governing attribute made structure.

The total friction dropped from 21 to 7 —67%— without touching the code. The same module map, the same dependencies, byte for byte identical. The only thing that changed was the organization: a single owner for the sellers surface, and the platform turned into a service. With that single change, two-thirds of the coordination friction disappeared. This is the inverse maneuver in action: we didn't redesign the system, we redesigned the organization, and the system —measured by its friction— improved as if we had redesigned it. We changed the terrain, and the river straightened. And notice this improvement is exactly what scalability needs: a surface with two-thirds less coordination friction is a surface that can change and scale fast, because each adjustment no longer drags three squads into a meeting.

The seller_api dropped from 6 to 0 pairs of coordination, and from 2 owners to 1. The module that was going to concentrate the worst friction —the public API, which in the trap was split between platform and orders and which depended on pieces scattered across four teams— became completely internal to a single team. In the trap, changing the Seller API forced coordinating 6 pairs of teams; after the maneuver, the seller_platform team changes it alone, without putting anyone else in the room, because it owns all its direct dependencies (onboarding, ingestion, payouts) and consumes auth as a service. That's the heart of a stream-aligned team: owner of the complete value flow, autonomous to change it. A sellers surface that can be changed without coordinating with anyone is, by definition, a surface that scales.

Here's the honest fine print, and it's important for the ADR you'll write later. The total friction didn't drop to zero: it stayed at 7, and that 7 is genuine coordination, not artificial. Breaking it down, two of those pairs are the real seams of the sellers surface with the rest of the business: listing_ingestion still coordinates with catalog (importing the external sellers' products writes to the catalog —it's a real dependency—) and payout_processing still coordinates with payments (paying the sellers uses the payment engine —also real—). The maneuver doesn't claim to abolish those seams, because they're the essence of the business: a real sellers surface has to put products into the catalog and pay people. What the maneuver eliminates is the artificial friction of bad organization (the four teams fighting over the API); what it leaves is the irreducible friction (the two seams the business really requires), and it lets them flow through the cheapest possible channel —a stable contract, not a shared kitchen—. Those two seams are precisely what the ADR of lesson 5 will name as "the two seams that require stable contracts", and what the rollout of lesson 6 will have to get the squads to adopt.

Seen as a team topology, the before and after are these:

flowchart TB
    subgraph BEFORE["BEFORE: the surface split up (pushing the water)"]
        direction LR
        p1["platform + orders<br/>seller_api, onboarding"]
        c1["catalog + platform<br/>listing_ingestion"]
        pay1["payments + platform<br/>payout_processing"]
        p1 <--> c1
        c1 <--> pay1
        p1 <--> pay1
    end
    subgraph AFTER["AFTER: one owning team (changing the terrain)"]
        direction LR
        sp["seller_platform<br/>(stream-aligned)<br/>owns the WHOLE surface"]
        cat["catalog"]
        paym["payments"]
        plat["platform<br/>(auth, notifications<br/>as a service)"]
        sp -->|contract: import listings| cat
        sp -->|contract: payouts| paym
        sp -.->|consume as-a-service| plat
    end

The diagram on the left is the trap: three blurry regions where several teams fight over each piece of the surface, with coordination in all directions (the friction 21). The one on the right is the maneuver: a stream-aligned team that owns the complete surface, with only two genuine seams toward catalog and payments (by contract) and the rest consumed as a service (the friction 7). One owner where there was a dispute.

Deep dive: why this piece depends on the two before it

The inverse maneuver is powerful, and like every powerful tool, misused it does harm. In the capstone its correct use depends on steps 1 and 2 having been done well —and there's the integration lesson—.

It depends on step 2 (the governing attribute), which gives it direction. The inverse maneuver designs the organization from the desired architecture, and the desired architecture is dictated by the governing attribute. Here the governing one is scalability, so the organization is designed to produce a surface that scales —a stream-aligned team owning the flow, autonomous—. If the governing one had been security (as in the installment payments project), the maneuver would have designed another organization —perhaps a platform capability that provides security controls, or boundaries around data isolation—. Applying the maneuver without having derived the governing attribute is reorganizing blind: moving teams without knowing what architecture they should produce, the anti-pattern of the "perpetual reorganizations" that leave everyone dizzy and the architecture the same. Step 3 needs the output of step 2 so as not to be a ritual.

It depends on step 1 (the framed role), which makes the maneuver the right remedy to the funnel. In lesson 2 you saw that the architect can't be the single channel of the 46 decisions, and that the way out was "structure the teams so the 38 local ones flow without them". The inverse maneuver is exactly that structuring: creating a team that owns the sellers surface is what makes the local decisions of that surface (how the ingestion is implemented, how the onboarding is structured) get made by that team, not the architect. The friction the maneuver reduces (21→7) is the same coordination that, if it weren't reduced, would have to pass through the funnel-architect's desk. The two steps are the two faces of the defense against the bottleneck: step 1 decides what isn't the architect's, step 3 creates the teams that absorb it.

And three criteria for when the maneuver is justified, so as not to operate on a healthy patient:

First, there's a measured misalignment and a clear target architecture. Here both are met: the avoidable friction is high (21 with the surface split up) and the target architecture is sharp (an independent sellers surface, which scalability demands). Without a clear target, reorganizing is shaking the org chart and hoping for luck.

Second, the maneuver derives the organization from the architecture, not the reverse. The correct order: (1) the governing attribute says what architecture you want (a surface that scales); (2) you design the organization that produces it (a team owning the surface); (3) you reorganize toward it; (4) you let the code flow. The mistake is reorganizing "because it was time" and seeing what comes out.

Third, the maneuver doesn't eliminate the necessary coordination, only the artificial. The friction dropped to 7, not 0, because the two seams (import listings ↔ catalog, payouts ↔ payments) are real. Forcing artificial boundaries to eliminate them —splitting the surface from its need to write to the catalog— would break the business. The well-done maneuver recognizes the irreducible coordination and lets it flow through stable contracts, instead of claiming to abolish it.

An honest nuance about the cost. The model treats "turning into a service" and "creating a team" as switches, and in reality they're work: defining the contracts, hiring and setting up the seller_platform team, transferring knowledge, weathering a few months of transition. The 67% is the destination, not the first day. What the number proves isn't that the reorganization is free, but that it's the right lever: the same effort invested in reorganizing yields an improvement that holds (because the organization stops pushing against), whereas invested only in rewriting code it yields an improvement that erodes. That cost and that transition are, in fact, part of what the evolution plan of lesson 7 is going to sequence.

Common mistakes

Putting the new surface in without giving it an owner (of the reorganization that doesn't happen). What happens: the change adds the Seller API and its pieces, but no one decides who owns them, so they get split up by proximity among the squads that "are nearby" —and the system is born with friction 21—. Why it happens: creating a new team feels like a management problem "for later", while putting in the code feels urgent. How to spot it: if any surface module has two or more owners in your map, you didn't apply the maneuver —you let the organizational accident dictate the structure—. How to fix it: before writing the surface, decide what team will own it completely; the single owner is what makes the friction 7 instead of 21, and it's an architect decision (cross-team), not management "for later".

Redesigning the surface code without changing the organization (of pushing the water). What happens: the team cleanly separates the Seller API in the code, celebrates, and in six months it's as coupled with catalog, orders, and payments as before, because the four teams keep touching it. Why it happens: rewriting feels like tangible progress; reorganizing feels political. How to spot it: if you separated modules but didn't change who owns them, you pushed the water with sandbags. How to fix it: invert the sequence —first the owning team (dig the channel), then the code flows and stays—; the maneuver exists precisely so the refactor doesn't erode.

Expecting the maneuver to bring the friction to zero (of denying the irreducible). What happens: the architect sees the friction dropped to 7 and gets frustrated, or worse, forces artificial boundaries to eliminate the two genuine seams —splitting the surface from its need to write to the catalog or to pay via payments—, and breaks the business. Why it happens: "reducing the artificial friction" is confused with "abolishing all coordination". How to spot it: if you're splitting dependencies that are the essence of the business (a sellers surface has to put in products and pay), you're denying the irreducible. How to fix it: accept that the real seams (import listings ↔ catalog, payouts ↔ payments) stay, and make them flow through the cheapest channel —a stable contract—; those two are precisely what the ADR will name as the conscious price and what the rollout will have to manage.

Exercises

Exercise 1 — Design the organization from the attribute. The governing attribute of this change is scalability, and that's why the maneuver created a stream-aligned team owning the surface. Suppose that, midway through the change, leadership adds a goal that triggers security as a co-governing one (for example, "meet a security certification to be able to integrate banks as sellers"). How would the organization you design change? Think about what new team or capability would appear.

See solution

With security as co-governing, in addition to the stream-aligned team a security platform capability would appear. The seller_platform team (stream-aligned, owner of the surface) still makes sense because scalability didn't disappear. But security at the same level asks for something a flow team doesn't provide well on its own: consistent, high-expertise security controls (robust third-party auth, auditing, data isolation, meeting the certification). That points to one of two Team Topologies patterns:

  • A platform team that provides security as a service —third-party auth, secrets management, auditing— that seller_platform (and the other squads) consume by contract, instead of each team implementing its own security inconsistently. This fits the "auth as a service" the maneuver already introduced, now reinforced.

  • A temporary enabling or complicated-subsystem team that helps seller_platform reach the certification's security level —because meeting a banking certification requires an expertise the flow team doesn't have and shouldn't have to develop from scratch—.

The integration lesson: the organization you design is faithful to the set of governing attributes. A single governing one (scalability) produced a single stream-aligned team; two governing ones (scalability + security) produce that team plus a platform or enabling capability that provides the second attribute without the flow team carrying all its complexity. This shows why step 2 (deriving the attributes) has to come before step 3: the structure is derived from what attributes have to be produced, and adding an attribute adds a piece to the organization.

Exercise 2 — The refactor that erodes. A lead, without waiting for the seller_platform team to form, dedicates a month to cleanly separating the Seller API from the monolith in the code, with impeccable boundaries. When they finish, the code is separated, but the team still doesn't exist: catalog, orders, and payments keep touching the surface. Predict, with Conway's Law, what will happen in the next six months, and what should have been done differently.

See solution

What's going to happen: the impeccably separated surface is going to re-couple. Since catalog, orders, and payments keep touching it (the organization didn't change), those teams are going to keep coordinating over that area, and every time they coordinate under pressure —an urgent bug, a feature with a deadline— they'll put in the fastest shortcut: a direct call to an internal surface module, a "temporary" shared data, a "just for now" cross dependency. In six months, the "separated" Seller API will be as intertwined with the rest as if it had never been touched. It's pushing the water with sandbags: the refactor erodes because the terrain of the communication (four teams touching the surface) didn't change, and the code flows back toward the shape that terrain dictates. The measured friction would stay near 21, not 7.

What should have been done differently: apply the inverse maneuver before or together with the refactor —form the seller_platform team (or at least assign the complete ownership of the surface to one team) and only then separate the code—. With a single owner, the separation holds: there aren't three teams pushing cross dependencies against it, so the boundary the owning team maintains isn't eroded by anyone from outside. The month of refactoring wasn't useless, but it was the second step executed as if it were the first. The capstone rule: the organizational structure (step 3) enables the code; without it, the code doesn't hold, no matter how clean it's born. First the team, then —and durably— the independent service.

Exercise 3 — Which seam is genuine and which is artificial? The maneuver dropped the friction to 7, not 0, because two seams are irreducible (import listings ↔ catalog; payouts ↔ payments). For each of these three dependencies that someone proposes to "eliminate by reorganizing", decide whether it's genuine coordination (leave it, make it a contract) or artificial (eliminate it with the maneuver), and why: (a) seller_platform has to write the sellers' products into the catalog; (b) seller_platform and platform fight over who maintains the Seller API code; (c) the payout to a seller has to go through the payments squad's payment engine.

See solution
  • (a) seller_platform writes to the catalog → GENUINE coordination. An external sellers surface exists so its products appear in the marketplace, and the marketplace is the catalog. Writing the listings into the catalog is the business; there's no way to "eliminate" that dependency without eliminating the surface's reason for being. The correct maneuver doesn't abolish it: it turns it into a stable contract (an ingestion API catalog exposes and seller_platform consumes), so the coordination is cheap instead of a shared kitchen. It's one of the two irreducible seams the ADR names. Leave it, make it a contract.

  • (b) seller_platform and platform fight over the Seller API → ARTIFICIAL coordination. Two teams fighting over who maintains the Seller API isn't a business necessity: it's an accident of bad ownership assignment (the "before" trap). This is exactly the friction the maneuver eliminates: the Seller API is owned by one team (seller_platform), period. Zero pairs of coordination over it (the seller_api: 6 → 0 of the worked example). Eliminate it with the maneuver by giving it a single owner.

  • (c) the payout goes through the payments engine → GENUINE coordination. Paying the external sellers means moving real money, and the payment engine (with its compliance, its gateway integration, its auditing) lives in payments. seller_platform shouldn't reimplement a payment engine —that would duplicate a critical and regulated capability—. The dependency is real: the correct coordination is a contract (payments exposes a payout API that seller_platform consumes), not merging the teams or seller_platform getting into the payments code. It's the second irreducible seam. Leave it, make it a contract.

The pattern: artificial coordination comes from several teams sharing ownership of the same component (it's eliminated by giving a single owner); genuine coordination comes from two distinct business capabilities that really need each other (it's kept, but made cheaper with a stable contract). The well-done maneuver distinguishes the two: it eliminates (b), keeps-as-contract (a) and (c). Confusing them is the mistake —abolishing a genuine seam breaks the business; keeping an artificial one leaves the friction at 21—.

Summary and next step

In this lesson you did step 3 of the deliverable: turning the governing attribute into organization with the inverse Conway maneuver. With the river and the channel you understood that pushing the water (redesigning the code) is a fight you lose every season, while changing the terrain (creating the owning team) straightens the river on its own. You measured it by executing: the sellers surface split up by proximity produces a friction of 21 pairs; giving it a stream-aligned team owning the complete surface and turning the platform into a service drops it to 7 (−67%) without touching the code, and the seller_api goes from 6 pairs of coordination to 0 —completely internal to one team, which is what scalability demands—. You understood that the friction doesn't drop to zero because two seams (import listings ↔ catalog, payouts ↔ payments) are irreducible and become contracts, not abolished; and that this piece depends on the two before it —the governing attribute gives it direction, the framed role makes it the remedy to the funnel—.

Before moving on you should be able to: apply the inverse maneuver —derive the organization that would produce the architecture the governing attribute demands—; measure the before and after of the friction; distinguish genuine coordination (made a contract) from artificial (eliminated with a single owner); and explain why reorganizing the code without the organization erodes.

What follows is step 4: communicating this decision. You already decided the structure —create seller_platform, turn the platform into a service, with two seams by contract—; lesson 5 teaches you to communicate it to each audience. You're going to draw the C4 of the change in mermaid —the Context for the VP (with the external seller now integrating via API) and the Container for the dev (with the new gateway and the seller_platform service)— and write the complete ADR-021 that packages the why of this decision for the future. The diagram communicates the what you just decided; the ADR communicates the why —including the two irreducible seams as the conscious price—.

Resources