The Dependency Map
Checkout depends on Cart, Inventory, Payment and Orders — but not all in the same way. Some must answer before the customer can be told anything; some can be told later. Drawing the edges, and marking which kind each is, turns a tree into an order of work and a design for what happens when a dependency is slow.
The situation, the reflex, and why it stalls
Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.
Which pieces of the decomposition need which others — and among those, which must be finished before you can respond to the user at all?
The tree is done and you want to start. You pick Checkout because it is the point of the store, and immediately you need a cart to read, products with stock, a payment provider that answers and an orders table to write. Everything you want to build first seems to need everything else built first.
Build bottom-up. Start with whatever has no dependencies — the products table, the product model — and work upward until checkout is reachable. It is the order a compiler would choose, and it guarantees nothing is built before what it needs.
Bottom-up puts the riskiest piece last. Payment is at the top of the dependency graph, so it is built after everything else, when a surprise from the provider — a forced redirect, a confirmation that arrives late — has the least time and the most code to disturb.
- Bottom-up puts the riskiest piece last. Payment is at the top of the dependency graph, so it is built after everything else, when a surprise from the provider — a forced redirect, a confirmation that arrives late — has the least time and the most code to disturb.
- It treats every edge as the same. "Checkout needs Cart" and "Checkout needs the provider's confirmation" are drawn identically, though the first is a function call and the second is a message from outside the system that may not come.
- The bottom layers are built to satisfy imagined needs of the layers above, so the products table grows columns for features not yet designed, and the order comes back as a schema migration later.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Draw the edges: for each piece, what must exist for it to work. Then mark each edge with one of two kinds. *Necessary before responding*: the user cannot be told anything until this answers — checkout cannot confirm without knowing whether payment succeeded. *Necessary eventually*: the piece needs it, but the user can be told something first — the confirmation email, the analytics event, the inventory report.
- The first kind sets the critical path and the latency of the user's experience; the second kind is what can be deferred, queued or retried without the user waiting. Naming which is which is a design decision that most systems make silently and wrongly.
- Use the map for two things: the order of work, where unblocked pieces come first and the riskiest necessary-before-responding dependency gets a spike early (Risk-First Development); and the shape of the code, where necessary-eventually edges become the natural place for background work (Which Dependency Must Answer Before the User Can Be Told Anything?).
- Stub what you cannot build yet. A dependency does not have to be finished to be depended on; it has to have an interface. Checkout can be built against a fake payment that always succeeds, and the fake is replaced when the spike is done (Treating External Systems as What They Are).
Checkout's edges, drawn
Solid edges are necessary before the customer can be answered; the dashed label marks the one that is necessary eventually. The external node is the only one on the critical path that can fail without us, which is why it gets a spike before Checkout is built against it for real.
From edges to an order of work
The map gives the constraint; risk and value choose within it. This order builds unblocked pieces first, stubs the external edge so Checkout can start, and runs the spike in parallel so the stub is replaced with something understood.
- 1Admin creates products; Catalog shows them
because No edges in; everything else reads products.
- 2Cart
because Its only edge is to Catalog, which now exists.
- 3Payment spike, in parallel: test-mode charge and confirmation handler with no store around them
because The only external edge on the critical path; a surprise here reshapes Checkout, so it must be learned before Checkout depends on it.
- 4Checkout against a fake payment: load, validate, total, create order, confirm
because Every internal edge is satisfied; the fake lets the whole path be tested while the spike finishes.
- 5Replace the fake with the real Payment leaf
because The spike answered "who says it is paid?"; now the answer becomes code.
- 6Email in the background
because Necessary eventually — the customer never waits on it.
Before responding, or eventually?
The decision is per edge, not per system, and it is the question this lesson exists to make askable. The options are the two kinds plus the honest third: an edge that must be attempted before responding but whose failure the user can be told about later.
For this edge into a user-facing piece: must it answer before the user is told anything?
when The user's next action depends on the answer — did I buy this? is it in stock?
cost Its latency and its failures are the user's; if it is external, the critical path now contains something you do not control.
when The user can act correctly without it — confirmation email, analytics, inventory report.
cost It must be guaranteed some other way (queue, retry, outbox), or "eventually" becomes "never" (The Transactional Outbox in Backend).
when The user must know it was started but not that it finished — a shipment request, a slow provider that acknowledges immediately.
cost Two states the UI must show, and a way to tell the user later; the state machine grows (Finding the State Machine).
How to do it
Most important first.
- For each piece, list what it reads and what it writes; edges follow from that. A piece that reads nothing and writes nothing is either a leaf to start on or a mistake.
- Mark every edge that crosses the system boundary — the payment provider, email — separately. Those are the ones that fail without you (External Systems Fail).
- For each edge into the piece that faces the user, ask: could the user be told something before this answers? If yes, it is necessary-eventually and a candidate for later or for the background.
- Order work: pieces with no unmet edges first; then whichever unblocked piece is riskiest or most valuable (What to Build First). Spike any boundary-crossing edge on the critical path before it is reached.
Worked on a concrete problem
The move has to produce something. This is what it produced.
- Checkout's edges: Cart (read the lines) — necessary before responding, internal. Inventory (is it in stock?) — necessary before responding, internal. Payment (did the charge succeed?) — necessary before responding, *external*. Orders (write the order) — necessary before responding, internal. Confirmation email — necessary eventually, external. So the critical path is Cart → Inventory → Payment → Orders → respond, and the one edge that can fail without us is on it.
- Order of work from the map: Admin and Catalog have no edges in, so they are first. Cart needs Catalog. Checkout needs Cart, Inventory, Payment, Orders — so Checkout is built against a fake payment as soon as Cart exists, and a payment spike runs in parallel because the real edge is external and on the critical path. Email is last and goes to the background from the start.
- A design decision the map exposed: inventory decrement was drawn as necessary-before-responding, and on inspection it need not be — the customer can be told "order placed" once payment succeeds, and the decrement can happen in the same transaction as the order write regardless of timing. But *checking* stock must be before responding, or the customer is told "placed" for something that is gone. The map split one word, "inventory", into two edges of different kinds (Invariants Under Concurrency).
How you know it worked
What now exists that did not before, and what question you can now ask.
- Every edge has a kind, and the critical path is a list you could read aloud.
- The riskiest edge on the critical path has a spike scheduled before the piece that depends on it.
- You can say which work happens after the user has already been answered — and it is not "nothing".
The questions you can now ask
The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.
- ?For this piece, what must exist before it can work — and which of those cross the boundary of my system?
- ?Could the user be told something before this dependency answers?
- ?Which unblocked piece is riskiest, and does it deserve a spike before anything depends on it?
- ?What would I stub to start now, and where is it written down that it is a stub?
What can go wrong
- Drawing the map with every edge necessary-before-responding, so the response waits on the email and the analytics, and the critical path is the whole system.
- Drawing every edge as deferrable, so the customer is told "order placed" before stock was checked or payment attempted, and the failure arrives by email an hour later.
- Stubbing a dependency and forgetting the stub is a stub. The fake payment that always succeeds ships, and the first real decline is handled by nothing.
- A map with edge kinds is more work than a list, and on a small system where everything is one function call the distinction adds nothing.
- Stubbing lets work start early at the price of a second integration later, when the stub is replaced and its assumptions turn out to have leaked.
- Deferring necessary-eventually work to the background buys latency and costs a new failure mode: the deferred thing may never happen (Background Jobs and Workers in Backend).
- "Build in dependency order." Dependency order is the constraint, not the plan. Within it, risk and value choose; and stubs let you violate it deliberately (Dependency First and Value First argue the two sides).
- "Necessary-eventually means unimportant." The confirmation email is not unimportant; it is just not something the customer waits for. Deferred work still has to be guaranteed, which is its own problem.
Where this applies
Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALEvery decomposition has edges, and the before-responding / eventually distinction applies wherever there is a caller waiting — a user, an upstream service, a batch scheduler with a deadline.
- DOMAIN-SPECIFICIn a store, payment is necessary before responding because the customer must know whether they bought something; in an analytics dashboard almost every edge is necessary-eventually because the reader tolerates staleness; in a chat app "message delivered" is before-responding and "read receipt" is eventually. The kinds are the same; which edges get which kind is the domain.
- ILLUSTRATIVECheckout's edges and the inventory split are invented for the running example; a store with reservations or a provider that answers asynchronously would draw a different critical path.
Where the depth lives
This domain asks the question and hands the answer off by name.