Skip to main content

sourcery_core/
aggregate.rs

1//! Command-side domain primitives.
2//!
3//! This module defines the building blocks for aggregates: state reconstruction
4//! (`Apply`) and command handling (`Handle`). The `#[derive(Aggregate)]` macro
5//! lives here to keep domain ergonomics in one spot.
6
7/// Command-side entities that produce domain events.
8///
9/// Aggregates rebuild their state from events (`Apply<E>`) and validate
10/// commands via [`Handle<C>`]. The derive macro generates the event enum and
11/// plumbing automatically, while keeping your state struct focused on domain
12/// behaviour.
13///
14/// Aggregates are domain objects and do not require serialisation by default.
15///
16/// If you enable snapshots (via `Repository::with_snapshots`), the aggregate
17/// state must be serialisable (`Serialize + DeserializeOwned`).
18// ANCHOR: aggregate_trait
19pub trait Aggregate: Default {
20    /// Aggregate type identifier used by the event store.
21    ///
22    /// This is combined with the aggregate ID to create stream identifiers.
23    /// Use lowercase, kebab-case for consistency: `"product"`,
24    /// `"user-account"`, etc.
25    const KIND: &'static str;
26
27    type Event;
28    type Error;
29    type Id;
30
31    /// Apply an event to update aggregate state.
32    ///
33    /// This is called during event replay to rebuild aggregate state from
34    /// history.
35    ///
36    /// When using `#[derive(Aggregate)]`, this dispatches to your `Apply<E>`
37    /// implementations. For hand-written aggregates, implement this
38    /// directly with a match expression.
39    fn apply(&mut self, event: &Self::Event);
40}
41// ANCHOR_END: aggregate_trait
42
43/// Mutate an aggregate with a domain event.
44///
45/// `Apply<E>` is called while the repository rebuilds aggregate state, keeping
46/// the domain logic focused on pure events rather than persistence concerns.
47///
48/// ```ignore
49/// #[derive(Default)]
50/// struct Account {
51///     balance: i64,
52/// }
53///
54/// impl Apply<FundsDeposited> for Account {
55///     fn apply(&mut self, event: &FundsDeposited) {
56///         self.balance += event.amount;
57///     }
58/// }
59/// ```
60// ANCHOR: apply_trait
61pub trait Apply<E> {
62    fn apply(&mut self, event: &E);
63}
64// ANCHOR_END: apply_trait
65
66/// Entry point for command handling.
67///
68/// Each command type gets its own implementation, letting the aggregate express
69/// validation logic in a strongly typed way.
70///
71/// ```ignore
72/// impl Handle<DepositFunds> for Account {
73///     fn handle(&self, command: &DepositFunds) -> Result<Vec<Self::Event>, Self::Error> {
74///         if command.amount <= 0 {
75///             return Err("amount must be positive".into());
76///         }
77///         Ok(vec![FundsDeposited { amount: command.amount }.into()])
78///     }
79/// }
80/// ```
81// ANCHOR: handle_trait
82pub trait Handle<C>: Aggregate {
83    /// Handle a command and produce events.
84    ///
85    /// # Errors
86    ///
87    /// Returns `Self::Error` if the command is invalid for the current
88    /// aggregate state.
89    fn handle(&self, command: &C) -> Result<Vec<Self::Event>, Self::Error>;
90}
91// ANCHOR_END: handle_trait