Identifying the invariants a workflow must never violate
coreintermediateAn invariant is a statement about your data that must be true before a workflow runs, after it runs, and after every failed or half-finished attempt at running it. "Inventory for a product is never negative" is an invariant. "A customer is charged at most once per order" is an invariant. "A row belonging to tenant A is never returned to tenant B" is an invariant. The point of writing them down is that a workflow spread across several requests, services and retries has no single place where correctness is obvious — a checkout touches a cart, a stock count, a payment provider and an order record, and no one of those four sees the whole rule. Naming the invariant separates the rule from any particular piece of code that happens to enforce it, so you can then ask the only question that matters: which mechanism actually guarantees this, and what happens to the rule when that mechanism is absent, retried, or run concurrently with a second copy of the same workflow. Without that list, correctness is defended only by whatever checks the code happens to contain, and those checks are usually written for the path where everything works.
Think of it as
Think of an invariant the way a bank thinks about its ledger: at every instant, however many transfers are mid-flight, the sum of all account balances must equal the total money in the bank. Nobody enforces that by hoping each transfer function is written carefully. They enforce it by making the rule explicit, then choosing a mechanism — double-entry bookkeeping — that makes violating it structurally hard rather than merely discouraged. Your job in a user workflow is the same two steps in the same order: state the rule in one sentence that a non-engineer could check, then pick the mechanism that holds it up. A rule you have not written down is a rule nobody owns.
What we're doing: Take one checkout workflow apart and list every invariant it is quietly responsible for.
- 12
- I1 is owned by step 1 alone, so a database constraint can hold it — this is the cheapest kind of invariant to enforce, and recognising that early saves you from reaching for a distributed mechanism you do not need.
- 16
- I3 and I4 span two systems that have no shared transaction, so no constraint can hold them. They need an explicit workflow with recorded state — which is why naming the invariant tells you the mechanism.
- 20
- I6 is not about this workflow at all; it is a rule every query in the system must obey. Invariants that apply everywhere want a mechanism that applies everywhere, such as row-level security, rather than a per-endpoint check that one endpoint will eventually forget.
Why this works: Reading the workflow as five steps makes it look like five small correctness problems. Reading it as six invariants shows the real shape: two are local and cheap to enforce, three cross a system boundary and need a workflow, and one is a global rule that no amount of care inside this workflow will hold up. The list, not the step diagram, is what tells you where to spend design effort.
Treating an application-level check as the enforcement mechanism
Wrong
Better
What you see: Stock counts go negative under load and only under load. The code reads correctly, passes review, and works in every test, because a test never runs two copies of the function against the same row in the same millisecond.
Why: The read and the write are two separate statements, so a second request can read the same stock value between them and pass the same check. The `if` describes the invariant without holding it; a `CHECK` constraint plus a conditional update makes the database itself refuse the violating write, which no amount of concurrency can talk it out of.
- Name the rule — one sentence, no implementation words
- Write the detecting query — what a violation looks like in the data
- List what can break it — concurrency, retries, partial failure
- Assign a mechanism — constraint, transaction, lock, idempotency, workflow
Four common invariants and the concurrent scenario that breaks each
Remember: An invariant is a rule that must hold before a workflow, after it, and after every failed, retried or concurrent attempt — "inventory never negative", "charged at most once", "no cross-tenant read". Write the rule and its detecting query down separately from the code, then enumerate what breaks it under concurrency and partial failure. An `if` statement in application code is a check, not a guarantee.
See also: choosing enforcement mechanisms · idempotency keys for post requests · isolation at every access path · designing for partial failure

