The store SPI, IdempotencyStore in idempotency-core, is five methods, and three of them
carry the protocol: tryAcquire,
complete and release. The other two are mechanical - extendLock is the engine’s
heartbeat and purgeExpired is the garbage collection.
Everything difficult about idempotency that is not the engine’s job lives behind the first
of the three: all blocking, waiting, and stale-lease stealing happens inside tryAcquire.
That concentration is deliberate. A store waits however its backend allows - the three shipped stores each poll, at an interval set for that backend - instead of the engine polling on one schedule it would have to tune for every backend.
The contract is the specification
IdempotencyStoreContract in idempotency-test is the single source of truth for store
behaviour.
-
Implement the SPI against your backend.
-
Extend the contract and implement
store().class MyStoreTest extends IdempotencyStoreContract { @Override protected IdempotencyStore store() { return new MyIdempotencyStore(...); } } -
Pass all of it. Not most of it - a store whose lease stealing is almost right is worse than one that does not support the feature, because the difference only appears under concurrency in production.
-
Override
completeInTransactionandsupportsTransactionalCompletion(), and extendTransactionalStoreContracttoo, if and only if your store can complete inside a caller’s transaction. Both default to declining, which is a supported answer:completealways means a write of its own, and the engine callscompleteInTransactiononly for joined completion.
Behaviour changes belong in the contract first
This is the rule that keeps three backends from becoming three subtly different libraries.
If your store cannot honour a behaviour - joined completion is the usual one - say so rather than approximating it. The engine fails the context at startup when something asks for what the store does not provide, which is a better outcome than an approximation that holds until it does not.
Before you write one
The three shipped stores cover a relational database, Redis, and a single JVM. A new backend
is worth writing when you run a durable store that is none of those and would rather not add
one. It is not worth writing to get a slightly different schema out of the JDBC store -
constructing that store by hand against a second DataSource is the cheaper answer.