#![allow(
clippy::shadow_reuse,
reason = "per-iteration Arc clones in try_fold closures intentionally re-bind"
)]
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::future::Future;
use core::marker::PhantomData;
use core::num::NonZeroU32;
use mnesis::{Aggregate, AggregateRoot, DomainEvent, EventOf, Events, Version};
use futures::TryStreamExt;
use crate::codec::{Decode, Encode};
use crate::envelope::{PersistedEnvelope, pending_envelope};
use crate::error::{AppendError, LoadWithError, StoreError};
use crate::store::{RawEventStore, Store};
use crate::stream_id::StreamKey;
use crate::upcasting::EventMorsel;
use crate::value::SchemaVersion;
pub trait Repository<A: Aggregate>: Send + Sync {
type Error: core::error::Error + Send + Sync + 'static;
fn load(&self, id: A::Id)
-> impl Future<Output = Result<AggregateRoot<A>, Self::Error>> + Send;
fn save<const N: usize>(
&self,
aggregate: &mut AggregateRoot<A>,
events: &Events<EventOf<A>, N>,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
}
pub(crate) trait ReplayFrom<A: Aggregate>: Send + Sync {
type Error: core::error::Error + Send + Sync + 'static;
fn replay_from(
&self,
root: AggregateRoot<A>,
from: Version,
) -> impl Future<Output = Result<AggregateRoot<A>, Self::Error>> + Send;
}
pub(super) fn version_to_nz32(version: Version) -> Option<NonZeroU32> {
let raw = version.as_u64();
let narrow = u32::try_from(raw).ok()?;
NonZeroU32::new(narrow)
}
pub(crate) const fn first_persisted_version(current: Option<Version>) -> Option<Version> {
match current {
None => Some(Version::INITIAL),
Some(v) => v.next(),
}
}
pub struct EventStore<S, C, A> {
store: Store<S>,
codec: Arc<C>,
_aggregate: PhantomData<fn() -> A>,
}
impl<S, C, A> EventStore<S, C, A> {
pub(crate) fn new(store: Store<S>, codec: C) -> Self {
Self {
store,
codec: Arc::new(codec),
_aggregate: PhantomData,
}
}
}
impl<S, C, A> ReplayFrom<A> for EventStore<S, C, A>
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,
{
type Error =
StoreError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error>;
async fn replay_from(
&self,
root: AggregateRoot<A>,
from: Version,
) -> Result<AggregateRoot<A>, Self::Error> {
let store = self.store.clone();
let codec = Arc::<C>::clone(&self.codec);
let raw_stream = store
.raw()
.read_stream(&StreamKey::from_slice(root.id().as_ref()), from)
.await
.map_err(StoreError::Adapter)?;
raw_stream
.map_err(StoreError::Adapter)
.try_fold(root, move |mut r, env| {
let codec = Arc::<C>::clone(&codec);
async move {
let version = env.version();
let out = <C as Decode<EventOf<A>>>::decode(&codec, &env)
.map_err(StoreError::Decode)?;
r.replay(version, out.borrow())?;
Ok(r)
}
})
.await
}
}
impl<S, C, A> Repository<A> for EventStore<S, C, A>
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,
{
type Error =
StoreError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error>;
async fn load(&self, id: A::Id) -> Result<AggregateRoot<A>, Self::Error> {
let root = AggregateRoot::<A>::new(id);
self.replay_from(root, Version::INITIAL).await
}
async fn save<const N: usize>(
&self,
aggregate: &mut AggregateRoot<A>,
events: &Events<EventOf<A>, N>,
) -> Result<(), Self::Error> {
save_events::<A, S, C, _, N>(&self.store, &self.codec, aggregate, events, |_| None).await
}
}
impl<S, C, A> EventStore<S, C, A> {
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: core::error::Error + Send + Sync + 'static,
EventOf<A>: DomainEvent,
S::Stream: Send,
{
let store = self.store.clone();
let codec = Arc::<C>::clone(&self.codec);
let root = AggregateRoot::<A>::new(id);
let raw_stream = store
.raw()
.read_stream(&StreamKey::from_slice(root.id().as_ref()), Version::INITIAL)
.await
.map_err(|e| LoadWithError::Store(StoreError::Adapter(e)))?;
let upcast = Arc::new(upcast);
raw_stream
.map_err(|e| LoadWithError::Store(StoreError::Adapter(e)))
.try_fold(root, move |mut r, env| {
let codec = Arc::<C>::clone(&codec);
let upcast = Arc::<F>::clone(&upcast);
async move {
let version = env.version();
let morsel = EventMorsel::borrowed(
env.event_type(),
env.schema_version_as_version(),
env.payload(),
);
let transformed = upcast(morsel).map_err(LoadWithError::Upcast)?;
let upcast_env = PersistedEnvelope::for_decode(
transformed.event_type(),
transformed.payload(),
)
.map_err(|e| LoadWithError::Store(StoreError::EnvelopeSynthesis(e)))?;
let out = <C as Decode<EventOf<A>>>::decode(&codec, &upcast_env)
.map_err(|e| LoadWithError::Store(StoreError::Decode(e)))?;
r.replay(version, out.borrow())
.map_err(|e| LoadWithError::Store(StoreError::Kernel(e)))?;
Ok(r)
}
})
.await
}
pub async fn save_with<F, const N: usize>(
&self,
aggregate: &mut AggregateRoot<A>,
events: &Events<EventOf<A>, N>,
current_version: F,
) -> Result<
(),
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,
{
save_events::<A, S, C, _, N>(&self.store, &self.codec, aggregate, events, current_version)
.await
}
}
async fn save_events<A, S, C, F, const N: usize>(
store: &Store<S>,
codec: &Arc<C>,
aggregate: &mut AggregateRoot<A>,
events: &Events<EventOf<A>, N>,
current_version: F,
) -> Result<
(),
StoreError<S::Error, <C as Encode<EventOf<A>>>::Error, <C as Decode<EventOf<A>>>::Error>,
>
where
A: Aggregate,
S: RawEventStore,
C: Encode<EventOf<A>> + Decode<EventOf<A>>,
F: Fn(&str) -> Option<Version>,
EventOf<A>: DomainEvent,
{
let expected_version = aggregate.version();
let mut next_version =
first_persisted_version(expected_version).ok_or(StoreError::VersionOverflow)?;
let mut envelopes = Vec::with_capacity(events.len());
let mut last_version = next_version;
for event in events {
let payload =
<C as Encode<EventOf<A>>>::encode(codec, event).map_err(StoreError::Encode)?;
let event_name = event.name();
let schema_version = current_version(event_name).unwrap_or(Version::INITIAL);
let schema_nz32 = version_to_nz32(schema_version).ok_or(StoreError::VersionOverflow)?;
let envelope = pending_envelope(next_version)
.event(event)
.payload(payload)
.schema_version(SchemaVersion::new(schema_nz32))
.build()?;
last_version = next_version;
envelopes.push(envelope);
if envelopes.len() < events.len() {
next_version = next_version.next().ok_or(StoreError::VersionOverflow)?;
}
}
store
.raw()
.append(
&StreamKey::from_slice(aggregate.id().as_ref()),
expected_version,
&envelopes,
)
.await
.map_err(|err| match err {
AppendError::Conflict {
stream_id,
expected,
actual,
} => StoreError::Conflict {
stream_id,
expected,
actual,
},
AppendError::Store(e) => StoreError::Adapter(e),
})?;
aggregate.commit_persisted(last_version, events);
Ok(())
}
#[cfg(test)]
mod version_helper_tests {
use super::first_persisted_version;
use mnesis::Version;
#[test]
fn fresh_stream_starts_at_initial() {
assert_eq!(first_persisted_version(None), Some(Version::INITIAL));
}
#[test]
fn existing_stream_advances_by_one() {
let v = Version::INITIAL;
assert_eq!(first_persisted_version(Some(v)), v.next());
}
#[test]
fn overflow_at_max_returns_none() {
let max = Version::new(u64::MAX).expect("u64::MAX is non-zero");
assert_eq!(first_persisted_version(Some(max)), None);
}
}