pub struct EventStore<S, C, A, M = ()> { /* private fields */ }Expand description
Event store over a single Encode + Decode codec — one terminal for
both owning and borrowing codecs.
The owning-vs-borrowing distinction is inferred from the codec’s
Decode::Output GAT, not restated at the call
site: an owning codec (Output<'a> = E, e.g. serde — one allocation per
decoded event) and a borrowing codec (Output<'a> = &'a E, e.g. a
#[repr(C)] POD reinterpret — zero allocation) are unified on the load
path by the bound Output<'a>: Borrow<E> (std’s Borrow<T> for T and
Borrow<T> for &T cover both), and the decoded value is fed to
replay via out.borrow() in either case.
§Construction
Created via Store::repository::<A>(),
which names the aggregate A once:
let store = Store::new(backend);
let orders = store.repository::<Order>().codec(OrderCodec).build();
let order = orders.load(id).await?; // AggregateRoot<Order> — inferred
orders.save(&mut order, &events).await?; // inferred§Aggregate binding
The aggregate A is a phantom type parameter (carried as
PhantomData<fn() -> A>, so the facade is Send + Sync + 'static
regardless of A and stays covariant in it). It exists solely so the
facade implements Repository<A> for exactly one A: with A
fixed on the type, load(id) / save(..) infer the aggregate from the
receiver, with no per-call annotation (the blanket-over-A impl that
previously defeated inference is gone). A is named once, at
repository::<A>(). The substrate Store<S> remains multi-aggregate;
mint one cheap per-aggregate facade per aggregate type.
§Schema evolution
The plain load / save path
performs no upcasting. For schema evolution, call
load_with with the macro-generated function
(e.g. OrderTransforms::upcast) on the read path, and
save_with with OrderTransforms::current_version
on the write path:
// Read path:
let root = es.load_with(id, OrderTransforms::upcast).await?;
// Write path:
es.save_with(&mut root, &events, OrderTransforms::current_version).await?;§Internal ownership
Owns the codec as Arc<C> and the metadata provider as Arc<M> so async
load paths can clone both handles into combinator closures and capture them
by value. Per Rust 2024’s stricter capture rules (RFC 3498, rustc issue
133529), a closure that borrows from &self and is then handed to a
try_fold-style combinator whose returned future is + Send cannot satisfy
the bound — the future-Send check effectively requires the borrow to be
'static. Owning the components via Arc and cloning per call sidesteps the
borrow entirely. Cost: one heap allocation at facade construction, one
pointer bump per load.
The M = () default keeps every existing call site compiling unchanged; the
inert provider always returns None metadata.
Implementations§
Source§impl<S, C, A, M> EventStore<S, C, A, M>
impl<S, C, A, M> EventStore<S, C, A, M>
Sourcepub async fn load_with<F, E>(
&self,
id: A::Id,
upcast: F,
) -> Result<AggregateRoot<A>, LoadWithError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error, E>>where
A: Aggregate,
S: RawEventStore + 'static,
for<'a> C: Encode<EventOf<A>> + Decode<EventOf<A>, Output<'a>: Borrow<EventOf<A>>> + 'static,
F: for<'a> Fn(EventMorsel<'a>) -> Result<EventMorsel<'a>, E> + Send + Sync + 'static,
E: Error + Send + Sync + 'static,
EventOf<A>: DomainEvent,
S::Stream: Send,
M: Send + Sync + 'static,
pub async fn load_with<F, E>(
&self,
id: A::Id,
upcast: F,
) -> Result<AggregateRoot<A>, LoadWithError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error, E>>where
A: Aggregate,
S: RawEventStore + 'static,
for<'a> C: Encode<EventOf<A>> + Decode<EventOf<A>, Output<'a>: Borrow<EventOf<A>>> + 'static,
F: for<'a> Fn(EventMorsel<'a>) -> Result<EventMorsel<'a>, E> + Send + Sync + 'static,
E: Error + Send + Sync + 'static,
EventOf<A>: DomainEvent,
S::Stream: Send,
M: Send + Sync + 'static,
Load an aggregate, running upcast over each persisted event
before decoding it.
upcast is the schema-evolution function — typically the
associated function the #[mnesis::transforms] macro emits
(e.g. OrderTransforms::upcast). Pass it directly as a function
pointer; the 'static bound on F and the + Send + Sync bounds
are required by the try_fold combinator chain (see the doc
comment on EventStore for the full Rust 2024 capture-rules
rationale).
§Errors
Returns LoadWithError::Store for any non-upcast error
(adapter, codec, kernel) and LoadWithError::Upcast for any
error returned by the upcast function.
Sourcepub async fn save_with<F, const N: usize>(
&self,
aggregate: &mut AggregateRoot<A>,
events: &Events<EventOf<A>, N>,
current_version: F,
) -> Result<S::AllPosition, StoreError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error>>where
A: Aggregate,
S: RawEventStore + 'static,
C: Encode<EventOf<A>> + Decode<EventOf<A>> + 'static,
F: Fn(&str) -> Option<Version>,
EventOf<A>: DomainEvent,
M: MetadataProvider<EventOf<A>>,
pub async fn save_with<F, const N: usize>(
&self,
aggregate: &mut AggregateRoot<A>,
events: &Events<EventOf<A>, N>,
current_version: F,
) -> Result<S::AllPosition, StoreError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error>>where
A: Aggregate,
S: RawEventStore + 'static,
C: Encode<EventOf<A>> + Decode<EventOf<A>> + 'static,
F: Fn(&str) -> Option<Version>,
EventOf<A>: DomainEvent,
M: MetadataProvider<EventOf<A>>,
Persist decided events, stamping the schema version on each via
current_version.
current_version is typically the associated function the
#[mnesis::transforms] macro emits (e.g.
OrderTransforms::current_version). For event types it doesn’t
know about, it returns None and the schema version falls back
to Version::INITIAL (the same default as the no-upcaster
save).
Returns the Position the last event landed at,
exactly as save does (#330).
§Errors
The same set of errors save can produce —
the schema-version lookup itself is infallible.
Trait Implementations§
Source§impl<S, C, A, M> Repository<A> for EventStore<S, C, A, M>where
A: Aggregate,
S: RawEventStore + 'static,
for<'a> C: Encode<EventOf<A>> + Decode<EventOf<A>, Output<'a>: Borrow<EventOf<A>>> + 'static,
EventOf<A>: DomainEvent,
S::Stream: Send,
M: MetadataProvider<EventOf<A>>,
impl<S, C, A, M> Repository<A> for EventStore<S, C, A, M>where
A: Aggregate,
S: RawEventStore + 'static,
for<'a> C: Encode<EventOf<A>> + Decode<EventOf<A>, Output<'a>: Borrow<EventOf<A>>> + 'static,
EventOf<A>: DomainEvent,
S::Stream: Send,
M: MetadataProvider<EventOf<A>>,
Source§type Error = StoreError<<S as RawEventStore>::Error, <C as Encode<<<A as Aggregate>::State as AggregateState>::Event>>::Error, <C as Decode<<<A as Aggregate>::State as AggregateState>::Event>>::Error>
type Error = StoreError<<S as RawEventStore>::Error, <C as Encode<<<A as Aggregate>::State as AggregateState>::Event>>::Error, <C as Decode<<<A as Aggregate>::State as AggregateState>::Event>>::Error>
Source§type Position = <S as RawEventStore>::AllPosition
type Position = <S as RawEventStore>::AllPosition
$all position save returns — the adapter’s
AllPosition, surfaced up from
RawEventStore::append (#330). Read moreAuto Trait Implementations§
impl<S, C, A, M> Freeze for EventStore<S, C, A, M>
impl<S, C, A, M> RefUnwindSafe for EventStore<S, C, A, M>
impl<S, C, A, M> Send for EventStore<S, C, A, M>
impl<S, C, A, M> Sync for EventStore<S, C, A, M>
impl<S, C, A, M> Unpin for EventStore<S, C, A, M>
impl<S, C, A, M> UnsafeUnpin for EventStore<S, C, A, M>
impl<S, C, A, M> UnwindSafe for EventStore<S, C, A, M>
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<A, R> CommandRepository<A> for Rwhere
A: Aggregate,
R: Repository<A>,
impl<A, R> CommandRepository<A> for Rwhere
A: Aggregate,
R: Repository<A>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.Source§impl<S, R> SagaRepository<S> for Rwhere
S: Saga,
R: Repository<S>,
impl<S, R> SagaRepository<S> for Rwhere
S: Saga,
R: Repository<S>,
Source§fn react_and_save<E, const N: usize>(
&self,
root: &mut AggregateRoot<S>,
event: &E,
) -> impl Future<Output = Result<Reaction<S, Self::Position, N>, SagaError<S::Error, Self::Error>>> + Sendwhere
S: React<E, N>,
E: DomainEvent,
fn react_and_save<E, const N: usize>(
&self,
root: &mut AggregateRoot<S>,
event: &E,
) -> impl Future<Output = Result<Reaction<S, Self::Position, N>, SagaError<S::Error, Self::Error>>> + Sendwhere
S: React<E, N>,
E: DomainEvent,
event against a saga root already in hand, persist any
produced own-events atomically, and return their intents pinned to the
versions save just assigned. No load — the caller supplies the root. Read moreSource§fn dispatch<E, const N: usize>(
&self,
id: S::Id,
event: &E,
) -> impl Future<Output = Result<Reaction<S, Self::Position, N>, SagaError<S::Error, Self::Error>>> + Sendwhere
S: React<E, N>,
E: DomainEvent,
fn dispatch<E, const N: usize>(
&self,
id: S::Id,
event: &E,
) -> impl Future<Output = Result<Reaction<S, Self::Position, N>, SagaError<S::Error, Self::Error>>> + Sendwhere
S: React<E, N>,
E: DomainEvent,
load the
instance then react_and_save. One call per
upstream event; a concurrent writer may cause save to surface
Err(SagaError::Store) with is_conflict — the
caller reloads and retries. load is whichever Repository<S>::load is
in play, so snapshot hydration composes for free. Read more