eventually_util/
optional.rs

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