Skip to content
v0.3

Write a decision

Read decisions first for what they are. This is how to write one.

The kernel is about to act, and the correct behaviour depends on a rule that is yours:

  • which warehouse serves this order
  • whether this customer gets this price
  • whether this cart qualifies for free shipping
  • whether this address is serviceable

If instead you want to react to something that already happened, you want an event. Deciding and reacting are different jobs, and using the wrong one is how a system gets race conditions nobody can name.

A decision is declared — a name, a question shape, an answer shape — and your implementation is called synchronously, inside the transaction the command is running in.

kernel: about to reserve stock
↓ asks
you: "warehouse wh_sp_01"
↓ uses the answer
kernel: reserves there, commits

The answer is a parameter of the change, not something applied afterwards. If your answer never comes, the command fails and nothing happened — no half-reserved stock, no order in a state you have to clean up.

Answer fast. You are holding a database transaction open. A slow decision is a slow checkout for every shopper at once, and the slowness is not local to the thing you are deciding about.

Answer deterministically. The same question should get the same answer. The kernel may be asking on a path that has to be reproducible, and a decision that consults something volatile makes the whole command non-reproducible with it.

Between them, those two rule out the tempting implementation: calling a third-party API from inside a decision. Pre-compute it, cache it, or make it a rule you can answer from data you already hold.

A decision belongs to an app — installed, consented, with its own storage if it needs any. That is what gives it a lifecycle: it can be installed, configured, and removed without leaving anything behind in the kernel.

Test the decision as a function: given this question, what answer. It has no side effects to mock, because a decision that has side effects is one that will be very hard to explain when it is called twice.

Then test the command that asks it, end to end, at least once — the interesting failures live in the seam rather than in either side.

  • Write through the port. You are inside the kernel’s transaction. Driving a command from in there is a reentrancy problem wearing a business rule.
  • Depend on wall-clock time in a way that changes the answer between two asks of the same question.
  • Fail open. If you cannot answer, say so. A decision that returns a default because it panicked has made a business decision by accident, and nobody will know it did.