A receipt delivery workflow needs a durable business write, a pending event, a recoverable claim and a replay rule at the HTTP edge.
Spring receipt delivery project: atomic write, guarded claim and replay
Build in observable steps
First make one database transaction insert a receipt and its outbox row, then force an exception and verify both disappear. The source-kit outbox test covers that local H2 boundary. Next claim the pending row with a guarded UPDATE and inspect its affected-row count. A failed or repeated claim must not be counted as new work.
At the HTTP edge, send the same idempotency key and request twice and compare the receipt identifier. Reuse the key with a changed payload and require a conflict. The MockMvc fixture checks this in one JVM; a real application needs persistent replay storage and a trusted tenant scope. Protect updates with an authorized revision precondition.
Define the unfinished production boundary
Add claim tokens and expiry, an idempotent consumer keyed by event ID, bounded retries, dead-letter handling, migrations and target-database concurrency tests before calling delivery resilient. Test the crash windows around send and acknowledgement. A broker acknowledgement is not the same as consumer application success. Expose queue age and failures through bounded metrics, and treat readiness separately from liveness.
Checked source
transaction.executeWithoutResult(status -> {
jdbc.update("insert into receipt values (?, ?)", "R-41", "accepted");
jdbc.update("insert into outbox values (?, ?, ?)", "E-41", "R-41", "pending");
});
int claimed = jdbc.update(
"update outbox set state = 'claimed' where id = ? and state = 'pending'", "E-41");Verification boundary
OutboxWriteContractTest.receiptAndOutboxRowCommitOrRollBackTogether runs in the downloadable Spring source kit. The excerpt leaves out surrounding setup and imports; the kit contains the complete test.
Costs and limits
The checked work uses one H2 process and one row. It is an acceptance seed, not a deployable delivery worker. Real throughput depends on database indexes, lock contention, batch size, broker latency and retry policy; none has been measured here.
Common Mistakes
- Do not equate a claimed row with confirmed consumer delivery.
- Do not retry a create request without a stable operation key.
- Do not expose the source kit as a production receipt service.
Read next
Spring transactional outbox: commit a receipt and event row together, Spring outbox claims: move pending work once per database state, Spring POST idempotency keys: replay the same receipt result, Spring readiness: report an unavailable dependency without forcing liveness failure.
Continue with checked worker recovery
Continue with Spring Boot receipt delivery project: add a recoverable worker contract.
