Move Optimistic Locking using Versions in AggregateRoot
ar3s3ru opened this issue · comments
Optimistic Locking through Versioning is a core functionality of Event Sourcing, because it allows:
- Concurrency update conflicts detection
- Idempotency
Given the simplistic design of an Aggregate
-- which only updates the State
and handles Commands
-- versioning should be part of the AggregateRoot
structure.
pub struct AggregateRoot<T> {
id: T::Id,
state: T::State,
version: u64,
// Other stuff
}
version
contains the current version of the AggregateRoot
, and it should be used by the Repository
when appending new events.
The EventStore
interface should change as such:
pub trait EventStore {
// As usual...
// The version to use for appending events must be specified during
// the function call: this way, callers can adhere to idempotency and OCC
// that will be handled by the underlying store implementations.
fn append(&mut self, id: Self::SourceId, version: u64, events: Vec<Self::Event>) -> ...
}
This might now mean that a difference must be made between new Domain Events (EventStore::Event
) and Events from the EventStream (committed events).
In short, this is how the EventStore
interface might look like:
pub struct PersistedEvent<T> {
version: u64,
sequence_number: u64,
committed_at: DateTime<Utc>, // Still unsure if needed
event: T,
}
pub trait EventStore {
// Same as usual ...
fn stream(&self, id: Self::SourceId, from: u64) ->
BoxFuture<Result<BoxStream<Result<PersistedEvent<Self::Event>, Self::Error>>, Self::Error>>
}
Very complicated type, let's break it down:
BoxFuture< // Asynchronous operation, requires a Future
Result< // Creating a Stream might fail (e.g. connection lost, etc.)
BoxStream< // The actual iterable EventStream
Result< // Since we're streaming from the network,
// there might be some errors when streaming an element.
PersistedEvent<Self::Event>, // The persisted representation of the Event.
Self::Error // The error in case the network fails
>
>,
Self::Error // Same as above
>
>