ConnectionsGENERALDOMAIN-SPECIFICILLUSTRATIVE

Problem Solving and Backend

User Workflow → Request → Business Rules → State → Dependencies. The backend is where the workflow becomes a request, the request meets the rules, the rules change state, and the state depends on things outside the process. Each arrow is a question; the backend domain answers them.

The moveWorked exampleNext questions

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.

The question

A workflow in the store has to become an endpoint. How does the loop get from "the customer checks out" to a request whose rules, state changes and external dependencies are all named, and what does it hand to the backend domain?

The situation

You are about to write the checkout endpoint. You know it takes a cart and returns an order. What happens between those two — which rules run, in which order, what gets written, what gets called, and what happens when the payment provider does not answer — is a blur you intend to resolve by writing code.

The reflex

Write the handler and discover the rules as you go. Validate the input, loop over the items, write the order, call the provider, return. Each step is obvious once you are typing it, and the handler grows into something that works on the happy path within the hour.

Why it stalls

The rules are discovered in the order the code reaches them, so the stock check happens after the order row is written, and a sold-out item produces an order that has to be deleted — a failure path invented by the implementation order (Implementation Order).

What the reflex produces — and fails to produce
  • The rules are discovered in the order the code reaches them, so the stock check happens after the order row is written, and a sold-out item produces an order that has to be deleted — a failure path invented by the implementation order (Implementation Order).
  • State changes are scattered through the handler and none is atomic with the others. The order exists, the stock is decremented, the provider is called, the provider fails — and the stock stays decremented for an order that will never be paid.
  • The provider call is in the middle of a database transaction, holding a lock for as long as the provider takes to answer, which under an outage is forever (External Calls Inside a Transaction in the backend domain is the lesson; this is how you end up needing it).
  • Nothing was written down, so the second engineer reads the handler to learn the rules and finds a rule that was a bug. The workflow was never stated; only its implementation was.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Run the chain: User Workflow → Request → Business Rules → State → Dependencies. The workflow is what the user does, in their words. The request is the boundary crossing — what comes in, what goes back, what identifies a repeat. The business rules are what must be true for the request to be allowed — stock, prices, ownership, limits. The state is what changes if the rules pass, and what must change together. The dependencies are what outside the process must answer, and what happens when it does not.
  • Write the five as a list before the handler, in that order. The order matters: rules come before state because a rule that fails must not have changed anything; state comes before dependencies because what is written and what is called must be separated — written together, called after (Which Dependency Must Answer Before the User Can Be Told Anything?).
  • For each dependency, write the four failure cases — timeout, error, late, duplicate — and what the state should be in each. This is the step the reflex skips entirely and the one that decides the handler's shape (What If Payment Fails?).
  • Hand off at the request. The backend domain teaches the request lifecycle, validation layers, transaction boundaries, idempotency keys and background jobs; each of those is a name for one arrow in the chain, and the chain says which you need.

The chain, as the endpoint's specification

Five steps, each a question, each answered before the handler. The backend domain owns the mechanisms behind steps two to five; this domain owns discovering that each step exists for this workflow.

User Workflow → Request → Business Rules → State → Dependencies
  1. 1
    User Workflow

    What the user does, in their words, and what they see at the end.

    fails by Starting from the endpoint's name.

  2. 2
    Request

    In, out, and what identifies a repeat — the idempotency question asked at design time.

    fails by In and out only; the repeat is a later bug.

  3. 3
    Business Rules

    Sentences that can be false, ordered cheapest-first, all checked before anything changes.

    fails by Discovered in the order the code reaches them.

  4. 4
    State

    What changes, with a box around what must change together — the transaction boundary.

    fails by Writes scattered through the handler, each its own transaction.

  5. 5
    Dependencies

    What outside the process must answer, called after the writes commit, with four failure cases each.

    fails by Called from inside the transaction; failure cases left to the try/catch.

The chain's output is a list that fits on a page and a handler that implements the list. The handler is shorter than the reflex's because its failure paths were designed once rather than patched repeatedly.

Checkout as a vertical slice

The chain designs the endpoint; the slice is how it is first built — thin, through every layer, proving the pieces connect before the failure cases are implemented. The slice device carries what a passing slice does not prove, which for checkout is most of the chain's last step.

Create order — the first slice of checkout
A customer with a cart clicks Pay and sees an order number
  1. FrontendSends cart id, payment token and a client-generated idempotency key; shows the returned order number.
  2. APIAccepts the request; rejects a missing key; returns order id and status.
  3. Business rulesChecks cart not empty, items in stock, prices current, cart owned by this session — in that order.
  4. StateOne transaction: order + items with snapshot prices, stock decrement, payment pending.
  5. DependencyAfter commit: a test-mode charge with the order id as reference; response recorded on the payment row.
proves
The five steps connect: a real cart becomes a real order with the right prices, stock moves, and the provider is called with a reference it will echo back.
does not prove
Any failure case. A timeout, a late callback, a duplicate callback and a provider error are all unhandled by this slice; it also does not prove that the idempotency key is honoured on a repeat, because the repeat path is not built. Those are the next slices, and the chain's dependency step is their specification.

The failure cases, as rows

The dependency step's output is a table like this one, written before the handler and implemented after the first slice. Each row names the state the store must be in afterwards; that column is the design, and the backend lessons supply the mechanism per row.

The payment provider, four ways
TriggerSymptomCauseResponse
Timeout on the charge callThe customer waits, then sees an error; the provider may or may not have charged.The answer was lost, not necessarily the charge.Payment stays pending; stock stays reserved; the customer is told; the provider's callback or a scheduled reconciliation resolves it (The Retry Is a Decision, Not a Reflex in the distributed domain).
Provider returns an errorThe customer sees a decline.A definite failure.Payment failed; stock released; order stays unpaid with a reason; the customer can retry with a new key.
Callback arrives after the order was cancelledA cancelled order suddenly shows paid.The provider was slower than the customer.The callback finds the order by reference; the transition from cancelled is a refund, never a paid state.
Callback arrives twiceTwo paid transitions, or a double email.At-least-once delivery from the provider.Unique provider reference; the transition is idempotent; the second callback is acknowledged and does nothing (Webhook Idempotency).

How to do it

Most important first.

  • Write the workflow in one sentence in the user's words, then the request as "in: …, out: …, repeat identified by: …". The third part is the idempotency question, asked before it is a bug (Duplicate Requests).
  • List the business rules as sentences that can be false: "every item is in stock", "prices match the current catalog", "the cart belongs to this session". Order them cheapest-first.
  • List the state changes and draw a box around the ones that must happen together. The box is the transaction boundary.
  • List the dependencies and, for each, the four failure cases with the state the store should be in after each. A blank is a decision not yet made, not a case that will not happen.
  • Only then write the handler — and expect it to be shorter than the reflex's, because the failure paths are designed rather than patched.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • Checkout, run through the chain. Workflow: the customer pays for their cart and gets a confirmation. Request: in — cart id, payment method token, an idempotency key from the client; out — order id and status; repeat — same key returns the same order. Rules: cart not empty; every item in stock; prices match; cart belongs to this session. State, together: order created with items and snapshot prices, stock decremented, payment row created as pending. Dependency: the provider, called after the transaction commits, with the order id as reference. Failure cases: timeout — leave payment pending, tell the customer, reconcile by the provider's callback or a scheduled check; error — mark payment failed, release stock, tell the customer; late confirmation — the callback finds the order by reference and transitions it, once; duplicate callback — the reference is unique and the transition is idempotent. The handler that follows is the one the backend lessons describe; it was designed by the list.
  • The file-upload service. Workflow: a user uploads an image and sees it on their profile. Request: in — a request for an upload slot; out — a presigned URL. Rules: the user is allowed to upload; the size is under the limit. State: a pending-upload row. Dependency: the object store, which the user talks to directly. Failure: the upload never completes — a scheduled job expires pending rows. The chain surfaced that the backend never touches the file, which changes the whole design (Case: A File Upload Service).
  • The chat app's send-message. Workflow: a member sends a message and the others see it. Request: in — conversation id, text, client message id; repeat — same client id, same message. Rules: sender is a member. State: message row. Dependency: the realtime fan-out, which must not be inside the write transaction and must tolerate a subscriber being offline. The chain's last step is the one that sends you to the backend domain's background-jobs and queue-semantics lessons.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • A five-part list exists for the endpoint before the handler does, and the handler is recognisably its implementation.
  • The repeat-identification question has an answer in the request definition, not in a bug report.
  • The transaction boundary is drawn around the state changes that must happen together, and no dependency call is inside it.
  • Every dependency has four failure cases written, and each names the state the store is left in.

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.

Next questions
  • ?What does the user do, in their words, and what crosses the boundary in and out — and what identifies a repeat?
  • ?Which rules must be true for this to be allowed, and in which order are they cheapest to check?
  • ?Which state changes must happen together, and where does the transaction end?
  • ?For each thing outside the process: what is the state after a timeout, an error, a late answer and a duplicate?

What can go wrong

How the move itself fails
  • The chain is run on every endpoint, including "get a product". Read endpoints have no rules, no state and usually no dependencies; the chain is for workflows that change things.
  • The failure cases are written and then the handler implements only the happy path "for now". The written cases are the specification; a handler that ignores them is the reflex with paperwork.
  • Dependencies are pushed out of the transaction so aggressively that the customer gets a confirmation before the payment is even attempted, with no pending state to explain it. Separating write from call means designing the in-between state, not hiding it.
  • The handoff is skipped and idempotency is re-invented from scratch. The backend domain has the mechanism; the chain's job was to say you need it.
What the move costs
  • The five-part list takes an hour the reflex would have spent typing, and on an endpoint with no dependencies and one state change it is an hour for a short list.
  • Designing the pending state between write and call adds a state to the model and a reconciliation path to build; the reflex's handler had neither and was wrong.
  • Failure cases written before the happy path exists are speculative until the provider is actually integrated; some will be revised. The revision is cheap; discovering them in production is not.
Misreads
  • "The chain is the request lifecycle." The lifecycle — parse, route, validate, handle, respond — is the backend domain's. The chain is what you must know about the workflow before the lifecycle can be filled in.
  • "Dependencies always go after the transaction." Usually, for writes. A rule that needs an external answer — a fraud check before the order is allowed — is a dependency at the rules step, and the chain places it there with its own failure cases.
  • "Writing the list means the handler is designed." It means the handler's shape is decided. The mechanisms — the transaction, the key, the job — still have to be built, and the backend lessons are how.

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.

  • GENERALAny request that changes state has rules, state and dependencies; the chain applies to a store, a chat app or an internal tool. What differs is how many dependencies there are and how much their failure costs.
  • DOMAIN-SPECIFICOn a payments path the failure cases are the design and must be complete before shipping; on an internal admin tool "tell the user and let them retry" is an acceptable answer to most of them, and the chain runs in ten minutes.
  • ILLUSTRATIVEThe checkout endpoint, the upload slot and the send-message request are invented to show the chain filling in; the provider's behaviour is a stand-in for whichever provider a real store uses.

Where the depth lives

This domain asks the question and hands the answer off by name.

Distributed Systemsretry-ambiguity
Further
  • The backend domain's request-lifecycle lesson is where the chain's second step becomes code; arrive with the five-part list and the failure table, and the lifecycle has content to fill.