eventually_core/
aggregate.rs

1//! Foundation traits for creating Domain abstractions
2//! using [the `Aggregate` pattern](https://martinfowler.com/bliki/DDD_Aggregate.html).
3
4use std::fmt::Debug;
5use std::ops::Deref;
6
7use futures::future::BoxFuture;
8
9#[cfg(feature = "serde")]
10use serde::Serialize;
11
12use crate::versioning::Versioned;
13
14/// A short extractor type for the Aggregate [`Id`].
15///
16/// [`Id`]: trait.Aggregate.html#associatedtype.Id
17pub type AggregateId<A> = <A as Aggregate>::Id;
18
19/// An Aggregate manages a domain entity [`State`], acting as a _transaction boundary_.
20///
21/// It allows **state mutations** through the use of [`Command`]s, which the
22/// Aggregate instance handles and emits a number of Domain [`Event`]s.
23///
24/// [`Event`]: trait.Aggregate.html#associatedtype.Event
25/// [`State`]: trait.Aggregate.html#associatedtype.State
26/// [`Command`]: trait.Aggregate.html#associatedtype.Command
27pub trait Aggregate {
28    /// Aggregate identifier: this should represent an unique identifier to refer
29    /// to a unique Aggregate instance.
30    type Id: Eq;
31
32    /// State of the Aggregate: this should represent the Domain Entity data structure.
33    type State: Default;
34
35    /// Represents a specific, domain-related change to the Aggregate [`State`].
36    ///
37    /// [`State`]: trait.Aggregate.html#associatedtype.State
38    type Event;
39
40    /// Commands are all the possible operations available on an Aggregate.
41    /// Use Commands to model business use-cases or [`State`] mutations.
42    ///
43    /// [`State`]: trait.Aggregate.html#associatedtype.State
44    type Command;
45
46    /// Possible failures while [`apply`]ing [`Event`]s or handling [`Command`]s.
47    ///
48    /// [`apply`]: trait.Aggregate.html#method.apply
49    /// [`Event`]: trait.Aggregate.html#associatedtype.Event
50    /// [`Command`]: trait.Aggregate.html#associatedtype.Command
51    type Error;
52
53    /// Applies an [`Event`] to the current Aggregate [`State`].
54    ///
55    /// To enforce immutability, this method takes ownership of the previous [`State`]
56    /// and the current [`Event`] to apply, and returns the new version of the [`State`]
57    /// or an error.
58    ///
59    /// [`State`]: trait.Aggregate.html#associatedtype.State
60    /// [`Event`]: trait.Aggregate.html#associatedtype.Event
61    fn apply(state: Self::State, event: Self::Event) -> Result<Self::State, Self::Error>;
62
63    /// Handles the requested [`Command`] and returns a list of [`Event`]s
64    /// to apply the [`State`] mutation based on the current representation of the State.
65    ///
66    /// [`Event`]: trait.Aggregate.html#associatedtype.Event
67    /// [`State`]: trait.Aggregate.html#associatedtype.State
68    /// [`Command`]: trait.Aggregate.html#associatedtype.Command
69    fn handle<'a, 's: 'a>(
70        &'a self,
71        id: &'s Self::Id,
72        state: &'s Self::State,
73        command: Self::Command,
74    ) -> BoxFuture<'a, Result<Option<Vec<Self::Event>>, Self::Error>>
75    where
76        Self: Sized;
77}
78
79/// Extension trait with some handy methods to use with [`Aggregate`]s.
80///
81/// [`Aggregate`]: trait.Aggregate.html
82pub trait AggregateExt: Aggregate {
83    /// Applies a list of [`Event`]s from an `Iterator`
84    /// to the current Aggregate [`State`].
85    ///
86    /// Useful to recreate the [`State`] of an Aggregate when the [`Event`]s
87    /// are located in-memory.
88    ///
89    /// [`State`]: trait.Aggregate.html#associatedtype.State
90    /// [`Event`]: trait.Aggregate.html#associatedtype.Event
91    #[inline]
92    fn fold<I>(state: Self::State, mut events: I) -> Result<Self::State, Self::Error>
93    where
94        I: Iterator<Item = Self::Event>,
95    {
96        events.try_fold(state, Self::apply)
97    }
98}
99
100impl<T> AggregateExt for T where T: Aggregate {}
101
102/// Builder type for new [`AggregateRoot`] instances.
103///
104/// [`AggregateRoot`]: struct.AggregateRoot.html
105#[derive(Clone)]
106pub struct AggregateRootBuilder<T>
107where
108    T: Aggregate,
109{
110    aggregate: T,
111}
112
113impl<T> From<T> for AggregateRootBuilder<T>
114where
115    T: Aggregate,
116{
117    #[inline]
118    fn from(aggregate: T) -> Self {
119        Self { aggregate }
120    }
121}
122
123impl<T> AggregateRootBuilder<T>
124where
125    T: Aggregate + Clone,
126{
127    /// Builds a new [`AggregateRoot`] instance for the specified Aggregate [`Id`].
128    ///
129    /// [`Id`]: trait.Aggregate.html#associatedtype.Id
130    /// [`AggregateRoot`]: struct.AggregateRoot.html
131    #[inline]
132    pub fn build(&self, id: T::Id) -> AggregateRoot<T> {
133        self.build_with_state(id, 0, Default::default())
134    }
135
136    /// Builds a new [`AggregateRoot`] instance for the specified Aggregate
137    /// with a specified [`State`] value.
138    ///
139    /// [`AggregateRoot`]: struct.AggregateRoot.html
140    /// [`State`]: trait.Aggregate.html#associatedtype.State
141    #[inline]
142    pub fn build_with_state(&self, id: T::Id, version: u32, state: T::State) -> AggregateRoot<T> {
143        AggregateRoot {
144            id,
145            version,
146            state,
147            aggregate: self.aggregate.clone(),
148            to_commit: None,
149        }
150    }
151}
152
153/// An `AggregateRoot` represents an handler to the [`Aggregate`] it's managing,
154/// such as:
155///
156/// * Owning its [`State`], [`Id`] and version,
157/// * Proxying [`Command`]s to the [`Aggregate`] using the current [`State`],
158/// * Keeping a list of [`Event`]s to commit after [`Command`] execution.
159///
160/// ## Initialize
161///
162/// An `AggregateRoot` can only be initialized using the [`AggregateRootBuilder`].
163///
164/// Check [`AggregateRootBuilder::build`] for more information.
165///
166/// [`Aggregate`]: trait.Aggregate.html
167/// [`AggregateExt`]: trait.AggregateExt.html
168/// [`root()`]: trait.AggregateExt.html#method.root
169/// [`Id`]: trait.Aggregate.html@associatedtype.Id
170/// [`Event`]: trait.Aggregate.html#associatedtype.Event
171/// [`State`]: trait.Aggregate.html#associatedtype.State
172/// [`Command`]: trait.Aggregate.html#associatedtype.Event
173/// [`AggregateRootBuilder`]: struct.AggregateRootBuilder.html
174/// [`AggregateRootBuilder::build`]: struct.AggregateRootBuilder.html#method.build
175#[derive(Debug)]
176#[cfg_attr(feature = "serde", derive(Serialize))]
177pub struct AggregateRoot<T>
178where
179    T: Aggregate + 'static,
180{
181    id: T::Id,
182    version: u32,
183
184    #[cfg_attr(feature = "serde", serde(flatten))]
185    state: T::State,
186
187    #[cfg_attr(feature = "serde", serde(skip_serializing))]
188    aggregate: T,
189
190    #[cfg_attr(feature = "serde", serde(skip_serializing))]
191    to_commit: Option<Vec<T::Event>>,
192}
193
194impl<T> PartialEq for AggregateRoot<T>
195where
196    T: Aggregate,
197{
198    #[inline]
199    fn eq(&self, other: &Self) -> bool {
200        self.id() == other.id()
201    }
202}
203
204impl<T> Versioned for AggregateRoot<T>
205where
206    T: Aggregate,
207{
208    #[inline]
209    fn version(&self) -> u32 {
210        self.version
211    }
212}
213
214impl<T> AggregateRoot<T>
215where
216    T: Aggregate,
217{
218    /// Returns a reference to the Aggregate [`Id`] that represents
219    /// the entity wrapped by this [`AggregateRoot`] instance.
220    ///
221    /// [`Id`]: trait.Aggregate.html#associatedtype.Id
222    /// [`AggregateRoot`]: struct.AggregateRoot.html
223    #[inline]
224    pub fn id(&self) -> &T::Id {
225        &self.id
226    }
227
228    /// Returns a reference to the current Aggregate [`State`].
229    ///
230    /// [`State`]: trait.Aggregate.html#associatedtype.State
231    #[inline]
232    pub fn state(&self) -> &T::State {
233        &self.state
234    }
235
236    /// Takes the list of events to commit from the current instance,
237    /// resetting it to `None`.
238    #[inline]
239    pub(crate) fn take_events_to_commit(&mut self) -> Option<Vec<T::Event>> {
240        std::mem::replace(&mut self.to_commit, None)
241    }
242
243    /// Returns a new `AggregateRoot` having the specified version.
244    #[inline]
245    pub(crate) fn with_version(mut self, version: u32) -> Self {
246        self.version = version;
247        self
248    }
249}
250
251impl<T> Deref for AggregateRoot<T>
252where
253    T: Aggregate,
254{
255    type Target = T::State;
256
257    fn deref(&self) -> &Self::Target {
258        self.state()
259    }
260}
261
262impl<T> AggregateRoot<T>
263where
264    T: Aggregate,
265    T::Event: Clone,
266    T::State: Clone,
267    T::Command: Debug,
268{
269    /// Handles the submitted [`Command`] using the [`Aggregate::handle`] method
270    /// and updates the Aggregate [`State`].
271    ///
272    /// Returns a `&mut self` reference to allow for _method chaining_.
273    ///
274    /// [`State`]: trait.Aggregate.html#associatedtype.State
275    /// [`Command`]: trait.Aggregate.html#associatedtype.Command
276    /// [`Aggregate::handle`]: trait.Aggregate.html#method.handle
277    #[cfg_attr(
278        feature = "with-tracing",
279        tracing::instrument(level = "debug", name = "AggregateRoot::handle", skip(self))
280    )]
281    pub async fn handle(&mut self, command: T::Command) -> Result<&mut Self, T::Error> {
282        let events = self
283            .aggregate
284            .handle(self.id(), self.state(), command)
285            .await?;
286
287        // Only apply new events if the command handling actually
288        // produced new ones.
289        if let Some(mut events) = events {
290            self.state = T::fold(self.state.clone(), events.clone().into_iter())?;
291            self.to_commit = Some(match self.to_commit.take() {
292                None => events,
293                Some(mut list) => {
294                    list.append(&mut events);
295                    list
296                }
297            });
298        }
299
300        Ok(self)
301    }
302}