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}