Skip to content

feat!: idempotency-key in context#82

Draft
bodymindarts wants to merge 2 commits intomainfrom
idempotency-key
Draft

feat!: idempotency-key in context#82
bodymindarts wants to merge 2 commits intomainfrom
idempotency-key

Conversation

@bodymindarts
Copy link
Copy Markdown
Member

@bodymindarts bodymindarts commented Feb 6, 2026

Summary

Add idempotency-key feature for context-based duplicate detection in the idempotency_guard! macro.

Solves the "retry vs new request" problem - e.g., two $100 withdrawals can't be distinguished by pattern matching alone. With idempotency keys, the external request ID identifies duplicates.

Changes

  • EventContext::set_idempotency_key() / idempotency_key() methods
  • ContextData::idempotency_key() accessor
  • idempotency_guard! now expects iter_persisted() (unified API)
  • Break pattern continues scanning for key matches after triggering
  • Updated book documentation

Usage

EventContext::current().set_idempotency_key(format!("payment-{}", payment_id));

idempotency_guard!(
self.events.iter_persisted().rev(),
OrderEvent::PaymentApplied { payment_id: pid, .. } if pid == &payment_id
);

@bodymindarts bodymindarts changed the title chore: bump flake chore: idempotency-key in context Feb 6, 2026
@bodymindarts bodymindarts changed the title chore: idempotency-key in context feat!: idempotency-key in context Feb 6, 2026
@bodymindarts bodymindarts force-pushed the idempotency-key branch 3 times, most recently from 3b69ad2 to c803f47 Compare February 6, 2026 11:01
Add a compile-time feature that extends the idempotency_guard! macro to
check for matching idempotency keys stored in event contexts, in addition
to the existing pattern-matching behavior.

When enabled:
- EventContext gains set_idempotency_key() and idempotency_key() methods
- ContextData gains idempotency_key() accessor
- idempotency_guard! checks both key matches and pattern matches
- Break pattern continues scanning for key matches after pattern break

Usage requires iter_persisted() instead of iter_all() to access
PersistedEvent items with context data.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@HonestMajority HonestMajority left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool solution! I like the magic part of it. Just have a couple of questions about side effects of this change

// systems to protect against replays.
idempotency_guard!(
self.events.iter_all().rev(),
self.events.iter_persisted().rev(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any implications of this change? That we are no longer protected from a double command inside one atomic db op? Just trying to think through if this is a problem or not. Does it mean we can call user.update_name() with the same name (and same idempotency id) in the same db op?


### Break Pattern Behavior

When using the break pattern (`=>`) with the `idempotency-key` feature, the macro continues scanning all events for idempotency key matches even after the break pattern matches. This ensures that duplicate requests are always detected regardless of where they appear in the event history:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normal case will be that there is no match for the idempotency key, meaning we generally will scan through the whole event history for each entity mutation, IIUC. What do you think about the performance implications of this? It is all in memory, so my guess is that it should generally be fine. Do you anticipate any problems, and any potential solutions?

@bodymindarts bodymindarts marked this pull request as draft February 6, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants