1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! Contains a different flavour of the [`Aggregate`] trait,
//! while still maintaining compatibility through [`AsAggregate`] type.
//!
//! Check out [`optional::Aggregate`] for more information.
//!
//! [`Aggregate`]: ../../eventually_core/aggregate/trait.Aggregate.html
//! [`AsAggregate`]: struct.AsAggregate.html
//! [`optional::Aggregate`]: trait.Aggregate.html
use futures::future::BoxFuture;
/// An `Option`-flavoured, [`Aggregate`]-compatible trait
/// to model Aggregates having an optional [`State`].
///
/// Use [`as_aggregate`] to get an [`Aggregate`]-compatible instance
/// of this trait.
///
/// [`Aggregate`]: ../../eventually_core/aggregate/trait.Aggregate.html
/// [`State`]: ../../eventually_core/aggregate/trait.Aggregate.html#associatedtype.State
/// [`as_aggregate`]: trait.Aggregate.html#method.as_aggregate
pub trait Aggregate {
/// Identifier type of the Aggregate.
///
/// Check out [`Aggregate::Id`] for more information.
///
/// [`Aggregate::State`]: ../../eventually_core/aggregate/trait.Aggregate.html#associatedtype.State
type Id: Eq;
/// State of the Aggregate.
///
/// Check out [`Aggregate::State`] for more information.
///
/// [`Aggregate::State`]: ../../eventually_core/aggregate/trait.Aggregate.html#associatedtype.State
type State;
/// Events produced and supported by the Aggregate.
///
/// Check out [`Aggregate::Event`] for more information.
///
/// [`Aggregate::Event`]: ../../eventually_core/aggregate/trait.Aggregate.html#associatedtype.Event
type Event;
/// Commands supported by the Aggregate.
///
/// Check out [`Aggregate::Command`] for more information.
///
/// [`Aggregate::Command`]: ../../eventually_core/aggregate/trait.Aggregate.html#associatedtype.Command
type Command;
/// Error produced by the the Aggregate while applying [`Event`]s
/// or handling [`Command`]s.
///
/// [`Event`]: trait.Aggregate.html#associatedtype.Event
/// [`Command`]: trait.Aggregate.html#associatedtype.Command
type Error;
/// Applies the specified [`Event`] when the [`State`] is empty.
///
/// [`Event`]: trait.Aggregate.html#associatedtype.Event
/// [`State`]: trait.Aggregate.html#associatedtype.State
fn apply_first(event: Self::Event) -> Result<Self::State, Self::Error>;
/// Applies the specified [`Event`] on a pre-existing [`State`] value.
///
/// [`Event`]: trait.Aggregate.html#associatedtype.Event
/// [`State`]: trait.Aggregate.html#associatedtype.State
fn apply_next(state: Self::State, event: Self::Event) -> Result<Self::State, Self::Error>;
/// Handles the specified [`Command`] when the [`State`] is empty.
///
/// [`Command`]: trait.Aggregate.html#associatedtype.Command
/// [`State`]: trait.Aggregate.html#associatedtype.State
fn handle_first<'s, 'a: 's>(
&'s self,
id: &'a Self::Id,
command: Self::Command,
) -> BoxFuture<'s, Result<Option<Vec<Self::Event>>, Self::Error>>
where
Self: Sized;
/// Handles the specified [`Command`] on a pre-existing [`State`] value.
///
/// [`Command`]: trait.Aggregate.html#associatedtype.Event
/// [`State`]: trait.Aggregate.html#associatedtype.State
fn handle_next<'a, 's: 'a>(
&'a self,
id: &'a Self::Id,
state: &'s Self::State,
command: Self::Command,
) -> BoxFuture<'a, Result<Option<Vec<Self::Event>>, Self::Error>>
where
Self: Sized;
/// Translates the current [`optional::Aggregate`] instance into
/// a _newtype instance_ compatible with the core [`Aggregate`] trait.
///
/// [`optional::Aggregate`]: trait.Aggregate.html
/// [`Aggregate`]: ../../eventually_core/aggregate/trait.Aggregate.html
#[inline]
fn as_aggregate(self) -> AsAggregate<Self>
where
Self: Sized,
{
AsAggregate::from(self)
}
}
/// _Newtype pattern_ to ensure compatibility between [`optional::Aggregate`] trait
/// and the core [`Aggregate`] trait.
///
/// ## Usage
///
/// 1. Use `From<Aggregate>` trait implementation:
/// ```text
/// use eventually_util::optional::AsAggregate;
///
/// let aggregate = AsAggregate::from(MyOptionalAggregate);
/// ```
/// 2. Use the [`Aggregate::as_aggregate`] method:
/// ```text
/// let aggregate = MyOptionalAggregate.as_aggregate();
/// ```
///
/// [`optional::Aggregate`]: trait.Aggregate.html
/// [`Aggregate::as_aggregate`]: trait.Aggregate.html#method.as_aggregate
/// [`Aggregate`]: ../../eventually_core/aggregate/trait.Aggregate.html
#[derive(Clone)]
pub struct AsAggregate<A>(A);
impl<A> From<A> for AsAggregate<A> {
#[inline]
fn from(value: A) -> Self {
AsAggregate(value)
}
}
impl<A> eventually_core::aggregate::Aggregate for AsAggregate<A>
where
A: Aggregate,
A: Send + Sync,
A::Id: Send + Sync,
A::Command: Send + Sync,
A::State: Send + Sync,
{
type Id = A::Id;
type State = Option<A::State>;
type Event = A::Event;
type Command = A::Command;
type Error = A::Error;
#[inline]
fn apply(state: Self::State, event: Self::Event) -> Result<Self::State, Self::Error> {
match state {
None => A::apply_first(event).map(Some),
Some(state) => A::apply_next(state, event).map(Some),
}
}
fn handle<'a, 's: 'a>(
&'a self,
id: &'s Self::Id,
state: &'s Self::State,
command: Self::Command,
) -> BoxFuture<'a, Result<Option<Vec<Self::Event>>, Self::Error>>
where
Self: Sized,
{
Box::pin(match state {
None => self.0.handle_first(id, command),
Some(state) => self.0.handle_next(id, state, command),
})
}
}