Interface IdempotencyStore
- All Known Implementing Classes:
InMemoryIdempotencyStore,JdbcIdempotencyStore
Every broker worth using delivers at least once, which means duplicates are not an error condition to be avoided but a normal event to be absorbed. A message is redelivered whenever a consumer dies between doing the work and acknowledging it, whenever a connection drops mid-delivery, and whenever a retry is triggered by something that had in fact succeeded.
The three-step shape is deliberate and is what makes this safe under failure. A naive store offers "have I seen this?" followed by "record that I have", and loses either way: mark before the handler runs and a crash mid-handler means the work never happens and never can, because the message now looks handled; mark after and two concurrent deliveries both pass the check. Claiming, then confirming only on success and releasing on failure, closes both.
Implementations must be safe under concurrent use and must make claim(java.lang.String) atomic:
exactly one caller can hold a claim on an identifier at a time.
-
Nested Class Summary
Nested Classes -
Method Summary
Modifier and TypeMethodDescriptionbooleanTakes ownership of a message identifier, if nobody else has it.voidRecords that the work for a claimed identifier completed.booleanisConfirmed(String messageId) voidGives up a claim without recording completion, so the message can be tried again.default IdempotencyStore.ClaimResultTakes ownership of a message identifier, and says why not when it cannot.
-
Method Details
-
claim
Takes ownership of a message identifier, if nobody else has it.- Parameters:
messageId- the identifier, normallyEnvelope.id()- Returns:
truewhen the caller now owns this identifier and should do the work;falsewhen it is already confirmed or claimed elsewhere. Which of the two it is matters — only a confirmed one is a duplicate — so consumers calltryClaim(java.lang.String)
-
confirm
Records that the work for a claimed identifier completed.After this,
claim(java.lang.String)returnsfalsefor the identifier until the store forgets it, which is what stops a redelivery from repeating the work.- Parameters:
messageId- the identifier being confirmed
-
release
Gives up a claim without recording completion, so the message can be tried again.Called when a handler fails. Without it a failed attempt would poison the identifier and the retry would be discarded as a duplicate, which turns a transient failure into permanent message loss.
- Parameters:
messageId- the identifier being released
-
isConfirmed
- Parameters:
messageId- the identifier to test- Returns:
- whether the work for this identifier is already known to have completed
-
tryClaim
Takes ownership of a message identifier, and says why not when it cannot.claim(java.lang.String)answersfalsefor two different situations, and a consumer must not treat them alike. A confirmed identifier is finished work, and a redelivery of it is a duplicate to acknowledge. A claimed but unconfirmed identifier is work nobody has finished: acknowledging that redelivery loses the message whenever the first handler died and its release failed too. So the consumer acknowledges onlyIdempotencyStore.ClaimResult.ALREADY_CONFIRMED, and sendsIdempotencyStore.ClaimResult.IN_PROGRESSback to the queue to be tried again.The default asks
claim(java.lang.String)and thenisConfirmed(java.lang.String), which suits every store written against the two-state contract. The two calls are not one atomic step, and both races resolve safely: a confirmation landing in between reads as a duplicate, which it is, and a confirmation expiring in between reads as in progress, which only costs a retry.- Parameters:
messageId- the identifier, normallyEnvelope.id()- Returns:
- whether the caller now owns the identifier, or why it does not
-