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