macro_rules! idempotency_guard {
($events:expr, $( $pattern:pat $(if $guard:expr)? ),+ $(,)?) => { ... };
($events:expr, $( $pattern:pat $(if $guard:expr)? ),+,
=> $break_pattern:pat $(if $break_guard:expr)?) => { ... };
}Expand description
Prevent duplicate event processing by checking for idempotent operations.
Guards against replaying the same mutation in event-sourced systems.
Returns AlreadyApplied early if matching events are found, allowing the caller
to skip redundant operations. Use break pattern to allow re-applying past operations.
§Parameters
$events: Event collection to search (usually chronologically reversed)$pattern: Event patterns that indicate operation already applied$break_pattern: Optional break pattern to stop searching
§Examples
use es_entity::{idempotency_guard, Idempotent};
pub enum UserEvent{
Initialized {id: u64, name: String},
NameUpdated {name: String}
}
pub struct User{
events: Vec<UserEvent>
}
impl User{
pub fn update_name(&mut self, new_name: impl Into<String>) -> Idempotent<()>{
let name = new_name.into();
idempotency_guard!(
self.events.iter().rev(),
UserEvent::NameUpdated { name: existing_name } if existing_name == &name
// above line returns early if same name found
);
self.events.push(UserEvent::NameUpdated{name});
Idempotent::Executed(())
}
pub fn update_name_with_break(&mut self, new_name: impl Into<String>) -> Idempotent<()>{
let name = new_name.into();
idempotency_guard!(
self.events.iter().rev(),
UserEvent::NameUpdated { name: existing_name } if existing_name == &name,
=> UserEvent::NameUpdated {..}
// above line breaks iteration if same event found
);
self.events.push(UserEvent::NameUpdated{name});
Idempotent::Executed(())
}
}
let mut user1 = User{ events: vec![] };
let mut user2 = User{ events: vec![] };
assert!(user1.update_name("Alice").did_execute());
// updating "ALice" again ignored because same event with same name exists
assert!(user1.update_name("Alice").was_already_applied());
assert!(user2.update_name_with_break("Alice").did_execute());
assert!(user2.update_name_with_break("Bob").did_execute());
// updating "ALice" again works because of early break condition
assert!(user2.update_name_with_break("Alice").did_execute());