Designing for Failure
What a design owes once calls can time out, repeat or half-succeed — including idempotency as a property chosen up front rather than retrofitted.
A function call either returns or throws. A remote call has a third outcome — you do not know — and an interface designed without a name for it will be wrong in a way no amount of error handling fixes.
Idempotency is a property of a signature, not a feature you add later. `createPayment(commandId, amount)` has the id in it because the failure model put it there — and no discipline around `createPayment(amount)` can substitute.
A local operation is all-or-nothing because the language and the transaction say so. A distributed one is not, and an interface that returns one boolean for five sub-operations is lying about what happened.
Retry safety is not something a caller can decide. It is a fact about the operation, and it has to be stated in the interface — otherwise every caller is guessing, and some of them will guess wrong.
Every piece of shared mutable state is a permanent tax on reasoning. Before reaching for a lock, ask whether ownership can be local, the data immutable, or the operation atomic — those remove the problem instead of managing it.
Whether a type may be used concurrently is part of its interface. Leaving it unsaid does not make it safe — it makes every caller guess, and the guesses are wrong at different times.
A function call becomes a message that may be lost, delayed, duplicated or half-processed. "We can split it later" underestimates this, because the cost is not the transport — it is every interface that was designed as if calls always return.