Skip to main content

arc_core/
aggregate.rs

1//! # Aggregate Module
2//!
3//! Core abstractions for domain aggregates and commands in event sourcing.
4//!
5//! ## Overview
6//!
7//! This module provides the traits needed to implement aggregates following the
8//! Event Sourcing and CQRS patterns. Aggregates are the fundamental building blocks
9//! that encapsulate business logic, enforce invariants, and produce events.
10//!
11//! ## Design Philosophy
12//!
13//! **Complexity is Opt-In**: You can use aggregates in two ways:
14//!
15//! 1. **Simple Path**: Define enums for commands/events, implement the trait
16//! 2. **Complex Path**: Add rich domain logic, validation, and business rules
17//!
18//! Both approaches use the same infrastructure and are first-class citizens.
19//!
20//! ## Core Concepts
21//!
22//! ### Commands
23//!
24//! Commands represent **intent** to change state. They are imperative (e.g., `CreateUser`,
25//! `UpdateProfile`) and can be rejected if business rules aren't satisfied.
26//!
27//! - Commands are validated before producing events
28//! - Commands may produce zero events (validation failure)
29//! - Commands may produce multiple events (complex operations)
30//! - Commands from one aggregate must be atomic
31//!
32//! ### Events
33//!
34//! Events represent **facts** about things that have happened. They are past tense
35//! (e.g., `UserCreated`, `ProfileUpdated`) and cannot be rejected once produced.
36//!
37//! - Events are immutable once written
38//! - Events are the source of truth
39//! - Events are used to reconstruct aggregate state
40//! - Events are published to event bus for subscribers
41//!
42//! ### Aggregates
43//!
44//! Aggregates are consistency boundaries that:
45//!
46//! - Encapsulate domain logic and business rules
47//! - Validate commands and produce events
48//! - Apply events to update internal state
49//! - Can be reconstructed from their event stream
50//! - Enforce invariants within their boundary
51//!
52//! ## Quick Start
53//!
54//! ### 1. Define Your Domain Events
55//!
56//! ```rust
57//! use serde::{Deserialize, Serialize};
58//!
59//! #[derive(Debug, Clone, Serialize, Deserialize)]
60//! pub enum UserEvent {
61//!     UserCreated {
62//!         id: String,
63//!         name: String,
64//!         email: String,
65//!     },
66//!     ProfileUpdated {
67//!         name: String,
68//!     },
69//!     EmailChanged {
70//!         email: String,
71//!     },
72//! }
73//! ```
74//!
75//! ### 2. Define Your Commands
76//!
77//! ```rust
78//! use arc_core::aggregate::Command;
79//! use serde::{Deserialize, Serialize};
80//!
81//! #[derive(Debug, Clone, Serialize, Deserialize)]
82//! pub enum UserCommand {
83//!     CreateUser {
84//!         id: String,
85//!         name: String,
86//!         email: String,
87//!     },
88//!     UpdateProfile {
89//!         id: String,
90//!         name: String,
91//!     },
92//!     ChangeEmail {
93//!         id: String,
94//!         email: String,
95//!     },
96//! }
97//!
98//! impl Command for UserCommand {
99//!     fn aggregate_id(&self) -> &str {
100//!         match self {
101//!             UserCommand::CreateUser { id, .. } => id,
102//!             UserCommand::UpdateProfile { id, .. } => id,
103//!             UserCommand::ChangeEmail { id, .. } => id,
104//!         }
105//!     }
106//! }
107//! ```
108//!
109//! ### 3. Define Your Aggregate
110//!
111//! ```rust
112//! use arc_core::aggregate::Aggregate;
113//! use arc_core::event::{Event, NewEvent};
114//! use thiserror::Error;
115//!
116//! # use serde::{Deserialize, Serialize};
117//! # use arc_core::aggregate::Command;
118//! #
119//! # #[derive(Debug, Clone, Serialize, Deserialize)]
120//! # pub enum UserEvent {
121//! #     UserCreated { id: String, name: String, email: String },
122//! #     ProfileUpdated { name: String },
123//! #     EmailChanged { email: String },
124//! # }
125//! #
126//! # #[derive(Debug, Clone, Serialize, Deserialize)]
127//! # pub enum UserCommand {
128//! #     CreateUser { id: String, name: String, email: String },
129//! #     UpdateProfile { id: String, name: String },
130//! #     ChangeEmail { id: String, email: String },
131//! # }
132//! #
133//! # impl Command for UserCommand {
134//! #     fn aggregate_id(&self) -> &str {
135//! #         match self {
136//! #             UserCommand::CreateUser { id, .. } => id,
137//! #             UserCommand::UpdateProfile { id, .. } => id,
138//! #             UserCommand::ChangeEmail { id, .. } => id,
139//! #         }
140//! #     }
141//! # }
142//! #
143//! #[derive(Debug, Error)]
144//! pub enum UserError {
145//!     #[error("User already exists")]
146//!     AlreadyExists,
147//!     #[error("User not found")]
148//!     NotFound,
149//!     #[error("Invalid email format")]
150//!     InvalidEmail,
151//! }
152//!
153//! #[derive(Default)]
154//! pub struct UserAggregate {
155//!     id: Option<String>,
156//!     name: Option<String>,
157//!     email: Option<String>,
158//!     version: i64,
159//!     created: bool,
160//! }
161//!
162//! #[async_trait::async_trait]
163//! impl Aggregate for UserAggregate {
164//!     type Command = UserCommand;
165//!     type Event = UserEvent;
166//!     type Error = UserError;
167//!
168//!     fn aggregate_type() -> &'static str {
169//!         "User"
170//!     }
171//!
172//!     fn version(&self) -> i64 {
173//!         self.version
174//!     }
175//!
176//!     async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
177//!         match command {
178//!             UserCommand::CreateUser { id, name, email } => {
179//!                 // Enforce invariant: user cannot be created twice
180//!                 if self.created {
181//!                     return Err(UserError::AlreadyExists);
182//!                 }
183//!
184//!                 // Validate email
185//!                 if !email.contains('@') {
186//!                     return Err(UserError::InvalidEmail);
187//!                 }
188//!
189//!                 // Produce event
190//!                 Ok(vec![Event::new(NewEvent {
191//!                             aggregate_type: "User",
192//!                             aggregate_id: &id,
193//!                             sequence: self.version + 1,
194//!                             event_type: "UserCreated",
195//!                             payload: serde_json::json!({
196//!                                                         "id": id,
197//!                                                         "name": name,
198//!                                                         "email": email,
199//!                                                     }),
200//!                         })])
201//!             }
202//!             UserCommand::UpdateProfile { id, name } => {
203//!                 if !self.created {
204//!                     return Err(UserError::NotFound);
205//!                 }
206//!
207//!                 Ok(vec![Event::new(NewEvent {
208//!                             aggregate_type: "User",
209//!                             aggregate_id: &id,
210//!                             sequence: self.version + 1,
211//!                             event_type: "ProfileUpdated",
212//!                             payload: serde_json::json!({ "name": name }),
213//!                         })])
214//!             }
215//!             UserCommand::ChangeEmail { id, email } => {
216//!                 if !self.created {
217//!                     return Err(UserError::NotFound);
218//!                 }
219//!
220//!                 if !email.contains('@') {
221//!                     return Err(UserError::InvalidEmail);
222//!                 }
223//!
224//!                 Ok(vec![Event::new(NewEvent {
225//!                             aggregate_type: "User",
226//!                             aggregate_id: &id,
227//!                             sequence: self.version + 1,
228//!                             event_type: "EmailChanged",
229//!                             payload: serde_json::json!({ "email": email }),
230//!                         })])
231//!             }
232//!         }
233//!     }
234//!
235//!     fn apply(&mut self, event: &Event) {
236//!         self.version = event.sequence;
237//!
238//!         match event.event_type.as_str() {
239//!             "UserCreated" => {
240//!                 self.id = Some(event.payload["id"].as_str().unwrap().to_string());
241//!                 self.name = Some(event.payload["name"].as_str().unwrap().to_string());
242//!                 self.email = Some(event.payload["email"].as_str().unwrap().to_string());
243//!                 self.created = true;
244//!             }
245//!             "ProfileUpdated" => {
246//!                 self.name = Some(event.payload["name"].as_str().unwrap().to_string());
247//!             }
248//!             "EmailChanged" => {
249//!                 self.email = Some(event.payload["email"].as_str().unwrap().to_string());
250//!             }
251//!             _ => {}
252//!         }
253//!     }
254//! }
255//! ```
256//!
257//! ### 4. Use Your Aggregate
258//!
259//! ```rust,no_run
260//! # use arc_core::aggregate::Aggregate;
261//! # use arc_core::event::{Event, NewEvent};
262//! # use thiserror::Error;
263//! # use serde::{Deserialize, Serialize};
264//! # use arc_core::aggregate::Command;
265//! #
266//! # #[derive(Debug, Clone, Serialize, Deserialize)]
267//! # pub enum UserEvent {
268//! #     UserCreated { id: String, name: String, email: String },
269//! #     ProfileUpdated { name: String },
270//! #     EmailChanged { email: String },
271//! # }
272//! #
273//! # #[derive(Debug, Clone, Serialize, Deserialize)]
274//! # pub enum UserCommand {
275//! #     CreateUser { id: String, name: String, email: String },
276//! #     UpdateProfile { id: String, name: String },
277//! #     ChangeEmail { id: String, email: String },
278//! # }
279//! #
280//! # impl Command for UserCommand {
281//! #     fn aggregate_id(&self) -> &str {
282//! #         match self {
283//! #             UserCommand::CreateUser { id, .. } => id,
284//! #             UserCommand::UpdateProfile { id, .. } => id,
285//! #             UserCommand::ChangeEmail { id, .. } => id,
286//! #         }
287//! #     }
288//! # }
289//! #
290//! # #[derive(Debug, Error)]
291//! # pub enum UserError {
292//! #     #[error("User already exists")]
293//! #     AlreadyExists,
294//! #     #[error("User not found")]
295//! #     NotFound,
296//! #     #[error("Invalid email format")]
297//! #     InvalidEmail,
298//! # }
299//! #
300//! # #[derive(Default)]
301//! # pub struct UserAggregate {
302//! #     id: Option<String>,
303//! #     name: Option<String>,
304//! #     email: Option<String>,
305//! #     version: i64,
306//! #     created: bool,
307//! # }
308//! #
309//! # #[async_trait::async_trait]
310//! # impl Aggregate for UserAggregate {
311//! #     type Command = UserCommand;
312//! #     type Event = UserEvent;
313//! #     type Error = UserError;
314//! #     fn aggregate_type() -> &'static str { "User" }
315//! #     fn version(&self) -> i64 { self.version }
316//! #     async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
317//! #         Ok(vec![])
318//! #     }
319//! #     fn apply(&mut self, event: &Event) {}
320//! # }
321//! #
322//! # async fn example() {
323//! // Create a command
324//! let command = UserCommand::CreateUser {
325//!     id: "user-123".to_string(),
326//!     name: "Alice".to_string(),
327//!     email: "alice@example.com".to_string(),
328//! };
329//!
330//! // Create aggregate (typically loaded from event store)
331//! let aggregate = UserAggregate::default();
332//!
333//! // Handle command
334//! let events = aggregate.handle(command).await.unwrap();
335//!
336//! // Events would be persisted to event store and published to event bus
337//! assert_eq!(events.len(), 1);
338//! assert_eq!(events[0].event_type, "UserCreated");
339//! # }
340//! ```
341//!
342//! ## Testing Your Aggregates
343//!
344//! Aggregates are easy to test because they're pure functions (commands → events → state).
345//!
346//! ```rust
347//! # use arc_core::aggregate::Aggregate;
348//! # use arc_core::event::{Event, NewEvent};
349//! # use thiserror::Error;
350//! # use serde::{Deserialize, Serialize};
351//! # use arc_core::aggregate::Command;
352//! #
353//! # #[derive(Debug, Clone, Serialize, Deserialize)]
354//! # pub enum UserEvent {
355//! #     UserCreated { id: String, name: String, email: String },
356//! # }
357//! #
358//! # #[derive(Debug, Clone, Serialize, Deserialize)]
359//! # pub enum UserCommand {
360//! #     CreateUser { id: String, name: String, email: String },
361//! # }
362//! #
363//! # impl Command for UserCommand {
364//! #     fn aggregate_id(&self) -> &str {
365//! #         match self {
366//! #             UserCommand::CreateUser { id, .. } => id,
367//! #         }
368//! #     }
369//! # }
370//! #
371//! # #[derive(Debug, Error)]
372//! # pub enum UserError {
373//! #     #[error("User already exists")]
374//! #     AlreadyExists,
375//! #     #[error("Invalid email format")]
376//! #     InvalidEmail,
377//! # }
378//! #
379//! # #[derive(Default)]
380//! # pub struct UserAggregate {
381//! #     id: Option<String>,
382//! #     version: i64,
383//! #     created: bool,
384//! # }
385//! #
386//! # #[async_trait::async_trait]
387//! # impl Aggregate for UserAggregate {
388//! #     type Command = UserCommand;
389//! #     type Event = UserEvent;
390//! #     type Error = UserError;
391//! #     fn aggregate_type() -> &'static str { "User" }
392//! #     fn version(&self) -> i64 { self.version }
393//! #     async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
394//! #         match command {
395//! #             UserCommand::CreateUser { id, name, email } => {
396//! #                 if self.created {
397//! #                     return Err(UserError::AlreadyExists);
398//! #                 }
399//! #                 if !email.contains('@') {
400//! #                     return Err(UserError::InvalidEmail);
401//! #                 }
402//! #                 Ok(vec![Event::new(NewEvent {
403//! #                             aggregate_type: "User",
404//! #                             aggregate_id: &id,
405//! #                             sequence: self.version + 1,
406//! #                             event_type: "UserCreated",
407//! #                             payload: serde_json::json!({ "id": id, "name": name, "email": email }),
408//! #                         })])
409//! #             }
410//! #         }
411//! #     }
412//! #     fn apply(&mut self, event: &Event) {
413//! #         self.version = event.sequence;
414//! #         if event.event_type == "UserCreated" {
415//! #             self.id = Some(event.aggregate_id.clone());
416//! #             self.created = true;
417//! #         }
418//! #     }
419//! # }
420//! #
421//! #[tokio::test]
422//! async fn test_user_creation() {
423//!     // Given: A new user aggregate
424//!     let aggregate = UserAggregate::default();
425//!
426//!     // When: Creating a user
427//!     let command = UserCommand::CreateUser {
428//!         id: "user-123".to_string(),
429//!         name: "Alice".to_string(),
430//!         email: "alice@example.com".to_string(),
431//!     };
432//!
433//!     let events = aggregate.handle(command).await.unwrap();
434//!
435//!     // Then: UserCreated event is produced
436//!     assert_eq!(events.len(), 1);
437//!     assert_eq!(events[0].event_type, "UserCreated");
438//!     assert_eq!(events[0].aggregate_id, "user-123");
439//! }
440//!
441//! #[tokio::test]
442//! async fn test_invalid_email() {
443//!     // Given: A new user aggregate
444//!     let aggregate = UserAggregate::default();
445//!
446//!     // When: Creating a user with invalid email
447//!     let command = UserCommand::CreateUser {
448//!         id: "user-456".to_string(),
449//!         name: "Bob".to_string(),
450//!         email: "not-an-email".to_string(), // Invalid!
451//!     };
452//!
453//!     let result = aggregate.handle(command).await;
454//!
455//!     // Then: Error is returned
456//!     assert!(result.is_err());
457//! }
458//!
459//! #[tokio::test]
460//! async fn test_cannot_create_twice() {
461//!     // Given: An existing user (reconstructed from events)
462//!     let mut aggregate = UserAggregate::default();
463//!     let event = Event::new(NewEvent {
464//!                     aggregate_type: "User",
465//!                     aggregate_id: "user-789",
466//!                     sequence: 1,
467//!                     event_type: "UserCreated",
468//!                     payload: serde_json::json!({
469//!                                     "id": "user-789",
470//!                                     "name": "Charlie",
471//!                                     "email": "charlie@example.com"
472//!                                 }),
473//!                 });
474//!     aggregate.apply(&event);
475//!
476//!     // When: Trying to create the user again
477//!     let command = UserCommand::CreateUser {
478//!         id: "user-789".to_string(),
479//!         name: "Charlie".to_string(),
480//!         email: "charlie@example.com".to_string(),
481//!     };
482//!
483//!     let result = aggregate.handle(command).await;
484//!
485//!     // Then: Error is returned
486//!     assert!(result.is_err());
487//! }
488//! ```
489//!
490//! ## Advanced Patterns
491//!
492//! ### Multiple Events from One Command
493//!
494//! Sometimes a command should produce multiple events atomically:
495//!
496//! ```rust,ignore
497//! async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
498//!     match command {
499//!         OrderCommand::PlaceOrder { order_id, items } => {
500//!             // Validate stock
501//!             // ...
502//!
503//!             // Produce multiple events
504//!             Ok(vec![
505//!                 Event::new(NewEvent {
506//!                     aggregate_type: "Order",
507//!                     aggregate_id: &order_id,
508//!                     sequence: self.version + 1,
509//!                     event_type: "OrderPlaced",
510//!                     payload: ...,
511//!                 }),
512//!                 Event::new(NewEvent {
513//!                     aggregate_type: "Order",
514//!                     aggregate_id: &order_id,
515//!                     sequence: self.version + 2,
516//!                     event_type: "InventoryReserved",
517//!                     payload: ...,
518//!                 }),
519//!                 Event::new(NewEvent {
520//!                     aggregate_type: "Order",
521//!                     aggregate_id: &order_id,
522//!                     sequence: self.version + 3,
523//!                     event_type: "PaymentRequested",
524//!                     payload: ...,
525//!                 }),
526//!             ])
527//!         }
528//!     }
529//! }
530//! ```
531//!
532//! ### Conditional Events
533//!
534//! Commands may produce zero events if preconditions aren't met:
535//!
536//! ```rust,ignore
537//! async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
538//!     match command {
539//!         UserCommand::MarkAsActive { id } => {
540//!             // If already active, no event needed
541//!             if self.is_active {
542//!                 return Ok(vec![]);
543//!             }
544//!
545//!             Ok(vec![Event::new(NewEvent {
546//!                         aggregate_type: "User",
547//!                         aggregate_id: &id,
548//!                         sequence: self.version + 1,
549//!                         event_type: "UserActivated",
550//!                         payload: ...,
551//!                     })])
552//!         }
553//!     }
554//! }
555//! ```
556//!
557//! ### Complex Validation
558//!
559//! Use the aggregate state to enforce complex business rules:
560//!
561//! ```rust,ignore
562//! async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
563//!     match command {
564//!         AccountCommand::Withdraw { amount } => {
565//!             // Check balance
566//!             if self.balance < amount {
567//!                 return Err(AccountError::InsufficientFunds);
568//!             }
569//!
570//!             // Check withdrawal limit
571//!             if self.daily_withdrawals + amount > self.daily_limit {
572//!                 return Err(AccountError::DailyLimitExceeded);
573//!             }
574//!
575//!             Ok(vec![Event::new(NewEvent {
576//!                         aggregate_type: "Account",
577//!                         aggregate_id: &self.id,
578//!                         sequence: self.version + 1,
579//!                         event_type: "Withdrawn",
580//!                         payload: ...,
581//!                     })])
582//!         }
583//!     }
584//! }
585//! ```
586
587use crate::event::Event;
588#[cfg(test)]
589use crate::event::NewEvent;
590use async_trait::async_trait;
591use std::error::Error;
592
593/// Trait for commands that can be dispatched to aggregates.
594///
595/// Commands represent intent to change aggregate state. They are imperative
596/// (e.g., `CreateUser`, `UpdateProfile`) and can be rejected if business rules
597/// aren't satisfied.
598///
599/// # Design Principles
600///
601/// - **Imperative naming**: Use verbs (Create, Update, Delete, Activate)
602/// - **Validation happens in aggregates**: Commands may be rejected
603/// - **Identify target**: Commands must know which aggregate instance to operate on
604/// - **Serializable**: Commands should be serializable for command sourcing
605///
606/// # Example
607///
608/// ```rust
609/// use arc_core::aggregate::Command;
610/// use serde::{Deserialize, Serialize};
611///
612/// #[derive(Debug, Clone, Serialize, Deserialize)]
613/// pub enum UserCommand {
614///     CreateUser { id: String, name: String, email: String },
615///     UpdateProfile { id: String, name: String },
616///     DeleteUser { id: String },
617/// }
618///
619/// impl Command for UserCommand {
620///     fn aggregate_id(&self) -> &str {
621///         match self {
622///             UserCommand::CreateUser { id, .. } => id,
623///             UserCommand::UpdateProfile { id, .. } => id,
624///             UserCommand::DeleteUser { id } => id,
625///         }
626///     }
627/// }
628/// ```
629pub trait Command: Send + Sync {
630    /// Get the aggregate instance ID this command targets.
631    ///
632    /// The command bus uses this to load the correct aggregate instance
633    /// from the event store.
634    ///
635    /// # Example
636    ///
637    /// ```rust
638    /// # use arc_core::aggregate::Command;
639    /// # use serde::{Deserialize, Serialize};
640    /// #
641    /// # #[derive(Debug, Clone, Serialize, Deserialize)]
642    /// # pub enum UserCommand {
643    /// #     CreateUser { id: String, name: String },
644    /// # }
645    /// #
646    /// # impl Command for UserCommand {
647    /// #     fn aggregate_id(&self) -> &str {
648    /// #         match self {
649    /// #             UserCommand::CreateUser { id, .. } => id,
650    /// #         }
651    /// #     }
652    /// # }
653    /// #
654    /// let command = UserCommand::CreateUser {
655    ///     id: "user-123".to_string(),
656    ///     name: "Alice".to_string(),
657    /// };
658    ///
659    /// assert_eq!(command.aggregate_id(), "user-123");
660    /// ```
661    fn aggregate_id(&self) -> &str;
662}
663
664/// Trait for domain aggregates in event sourcing.
665///
666/// Aggregates are consistency boundaries that encapsulate business logic,
667/// enforce invariants, and produce events in response to commands.
668///
669/// # Lifecycle
670///
671/// 1. **Load**: Reconstruct aggregate from event stream using `from_events()`
672/// 2. **Command**: Handle command with `handle()` to produce new events
673/// 3. **Store**: Persist events to event store (done by command bus)
674/// 4. **Apply**: Apply new events to update aggregate state using `apply()`
675/// 5. **Publish**: Publish events to event bus (done by command bus)
676///
677/// # Design Principles
678///
679/// - **State is private**: Aggregate state should not be exposed outside
680/// - **Commands produce events**: `handle()` validates and produces events
681/// - **Events update state**: `apply()` updates internal state deterministically
682/// - **Pure functions**: `handle()` has no side effects (no I/O, no mutations)
683/// - **Deterministic apply**: Same events always produce same state
684/// - **Default implementation**: Aggregate must implement `Default` for initial state
685///
686/// # Type Parameters
687///
688/// - `Command`: The command type this aggregate handles (must implement `Command`)
689/// - `Event`: The domain event type (typically an enum, must be serializable)
690/// - `Error`: The error type for validation failures (must implement `std::error::Error`)
691///
692/// # Example
693///
694/// See the module-level documentation for a complete example.
695#[async_trait]
696pub trait Aggregate: Send + Sync + Default {
697    /// The command type this aggregate handles
698    type Command: Command;
699
700    /// The domain event type (your custom enum)
701    type Event;
702
703    /// The error type for validation failures
704    type Error: Error + Send + Sync + 'static;
705
706    /// Get the aggregate type name (e.g., "User", "Order", "Account").
707    ///
708    /// This is used for event metadata and debugging. Should be a static string
709    /// that uniquely identifies this aggregate type in your domain.
710    ///
711    /// # Example
712    ///
713    /// ```rust
714    /// # use arc_core::aggregate::{Aggregate, Command};
715    /// # use arc_core::event::{Event, NewEvent};
716    /// # use thiserror::Error;
717    /// #
718    /// # #[derive(Debug)]
719    /// # struct DummyCommand;
720    /// # impl Command for DummyCommand {
721    /// #     fn aggregate_id(&self) -> &str { "dummy" }
722    /// # }
723    /// #
724    /// # #[derive(Debug, Error)]
725    /// # #[error("dummy error")]
726    /// # struct DummyError;
727    /// #
728    /// # #[derive(Default)]
729    /// # struct UserAggregate;
730    /// #
731    /// # #[async_trait::async_trait]
732    /// # impl Aggregate for UserAggregate {
733    /// #     type Command = DummyCommand;
734    /// #     type Event = ();
735    /// #     type Error = DummyError;
736    /// #
737    /// fn aggregate_type() -> &'static str {
738    ///     "User"
739    /// }
740    /// #
741    /// #     fn version(&self) -> i64 { 0 }
742    /// #     async fn handle(&self, _: Self::Command) -> Result<Vec<Event>, Self::Error> { Ok(vec![]) }
743    /// #     fn apply(&mut self, _: &Event) {}
744    /// # }
745    /// #
746    /// assert_eq!(UserAggregate::aggregate_type(), "User");
747    /// ```
748    fn aggregate_type() -> &'static str;
749
750    /// Get the current version (sequence number) of this aggregate.
751    ///
752    /// The version represents how many events have been applied to this aggregate.
753    /// It starts at 0 for a new aggregate and increments with each event.
754    ///
755    /// This is used for optimistic concurrency control - the event store checks
756    /// that the version hasn't changed since the aggregate was loaded.
757    ///
758    /// # Example
759    ///
760    /// ```rust
761    /// # use arc_core::aggregate::{Aggregate, Command};
762    /// # use arc_core::event::{Event, NewEvent};
763    /// # use thiserror::Error;
764    /// #
765    /// # #[derive(Debug)]
766    /// # struct DummyCommand;
767    /// # impl Command for DummyCommand {
768    /// #     fn aggregate_id(&self) -> &str { "dummy" }
769    /// # }
770    /// #
771    /// # #[derive(Debug, Error)]
772    /// # #[error("dummy error")]
773    /// # struct DummyError;
774    /// #
775    /// # #[derive(Default)]
776    /// # struct UserAggregate { version: i64 }
777    /// #
778    /// # #[async_trait::async_trait]
779    /// # impl Aggregate for UserAggregate {
780    /// #     type Command = DummyCommand;
781    /// #     type Event = ();
782    /// #     type Error = DummyError;
783    /// #     fn aggregate_type() -> &'static str { "User" }
784    /// #
785    /// fn version(&self) -> i64 {
786    ///     self.version
787    /// }
788    /// #
789    /// #     async fn handle(&self, _: Self::Command) -> Result<Vec<Event>, Self::Error> { Ok(vec![]) }
790    /// #     fn apply(&mut self, _: &Event) {}
791    /// # }
792    /// #
793    /// let aggregate = UserAggregate::default();
794    /// assert_eq!(aggregate.version(), 0); // New aggregate
795    /// ```
796    fn version(&self) -> i64;
797
798    /// Handle a command and produce events.
799    ///
800    /// This is where your business logic lives. The method should:
801    ///
802    /// 1. Inspect current state (`self`) to make decisions
803    /// 2. Validate the command against business rules
804    /// 3. Return error if validation fails
805    /// 4. Produce one or more events if validation succeeds
806    /// 5. Return empty vec if command has no effect
807    ///
808    /// **Important**: This method should have no side effects:
809    /// - Don't write to databases
810    /// - Don't call external APIs
811    /// - Don't mutate state
812    ///
813    /// Side effects happen in projections and event handlers.
814    ///
815    /// # Arguments
816    ///
817    /// - `command`: The command to handle
818    ///
819    /// # Returns
820    ///
821    /// - `Ok(Vec<Event>)`: One or more events to persist and publish
822    /// - `Err(Self::Error)`: Validation or business rule failure
823    ///
824    /// # Example
825    ///
826    /// ```rust,ignore
827    /// async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
828    ///     match command {
829    ///         UserCommand::CreateUser { id, name, email } => {
830    ///             // Check invariant
831    ///             if self.created {
832    ///                 return Err(UserError::AlreadyExists);
833    ///             }
834    ///
835    ///             // Validate input
836    ///             if !email.contains('@') {
837    ///                 return Err(UserError::InvalidEmail);
838    ///             }
839    ///
840    ///             // Produce event
841    ///             Ok(vec![Event::new(NewEvent {
842    ///                         aggregate_type: "User",
843    ///                         aggregate_id: &id,
844    ///                         sequence: self.version + 1,
845    ///                         event_type: "UserCreated",
846    ///                         payload: ...,
847    ///                     })])
848    ///         }
849    ///     }
850    /// }
851    /// ```
852    async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error>;
853
854    /// Apply an event to update aggregate state.
855    ///
856    /// This method must be **deterministic** and **side-effect free**:
857    /// - Same events always produce same state
858    /// - No I/O operations
859    /// - No randomness
860    /// - No external dependencies
861    ///
862    /// The event is already persisted when this is called. Your job is to
863    /// update the aggregate's internal state to reflect the event.
864    ///
865    /// # Arguments
866    ///
867    /// - `event`: The event to apply (already persisted)
868    ///
869    /// # Example
870    ///
871    /// ```rust,ignore
872    /// fn apply(&mut self, event: &Event) {
873    ///     self.version = event.sequence;
874    ///
875    ///     match event.event_type.as_str() {
876    ///         "UserCreated" => {
877    ///             self.id = Some(event.payload["id"].as_str().unwrap().to_string());
878    ///             self.name = Some(event.payload["name"].as_str().unwrap().to_string());
879    ///             self.created = true;
880    ///         }
881    ///         "ProfileUpdated" => {
882    ///             self.name = Some(event.payload["name"].as_str().unwrap().to_string());
883    ///         }
884    ///         _ => {} // Unknown event types are ignored
885    ///     }
886    /// }
887    /// ```
888    fn apply(&mut self, event: &Event);
889
890    /// Reconstruct aggregate from its event stream.
891    ///
892    /// This method has a default implementation that:
893    /// 1. Creates a new aggregate instance using `Default::default()`
894    /// 2. Applies each event in order using `apply()`
895    /// 3. Returns the reconstructed aggregate
896    ///
897    /// You typically don't need to override this unless you have special requirements.
898    ///
899    /// # Arguments
900    ///
901    /// - `events`: The complete event stream for this aggregate
902    ///
903    /// # Returns
904    ///
905    /// A fully reconstructed aggregate with all events applied
906    ///
907    /// # Example
908    ///
909    /// ```rust
910    /// # use arc_core::aggregate::{Aggregate, Command};
911    /// # use arc_core::event::{Event, NewEvent};
912    /// # use thiserror::Error;
913    /// #
914    /// # #[derive(Debug)]
915    /// # struct DummyCommand;
916    /// # impl Command for DummyCommand {
917    /// #     fn aggregate_id(&self) -> &str { "dummy" }
918    /// # }
919    /// #
920    /// # #[derive(Debug, Error)]
921    /// # #[error("dummy error")]
922    /// # struct DummyError;
923    /// #
924    /// # #[derive(Default)]
925    /// # struct UserAggregate {
926    /// #     id: Option<String>,
927    /// #     version: i64,
928    /// #     created: bool,
929    /// # }
930    /// #
931    /// # #[async_trait::async_trait]
932    /// # impl Aggregate for UserAggregate {
933    /// #     type Command = DummyCommand;
934    /// #     type Event = ();
935    /// #     type Error = DummyError;
936    /// #     fn aggregate_type() -> &'static str { "User" }
937    /// #     fn version(&self) -> i64 { self.version }
938    /// #     async fn handle(&self, _: Self::Command) -> Result<Vec<Event>, Self::Error> { Ok(vec![]) }
939    /// #
940    /// #     fn apply(&mut self, event: &Event) {
941    /// #         self.version = event.sequence;
942    /// #         if event.event_type == "UserCreated" {
943    /// #             self.id = Some(event.aggregate_id.clone());
944    /// #             self.created = true;
945    /// #         }
946    /// #     }
947    /// # }
948    /// #
949    /// // Load events from event store
950    /// let events = vec![
951    ///     Event::new(NewEvent {
952    ///         aggregate_type: "User",
953    ///         aggregate_id: "user-123",
954    ///         sequence: 1,
955    ///         event_type: "UserCreated",
956    ///         payload: serde_json::json!({"id": "user-123"}),
957    ///     }),
958    ///     Event::new(NewEvent {
959    ///         aggregate_type: "User",
960    ///         aggregate_id: "user-123",
961    ///         sequence: 2,
962    ///         event_type: "ProfileUpdated",
963    ///         payload: serde_json::json!({"name": "Alice"}),
964    ///     }),
965    /// ];
966    ///
967    /// // Reconstruct aggregate
968    /// let aggregate = UserAggregate::from_events(events);
969    ///
970    /// assert_eq!(aggregate.version(), 2);
971    /// assert!(aggregate.created);
972    /// ```
973    fn from_events(events: Vec<Event>) -> Self {
974        let mut aggregate = Self::default();
975        for event in events {
976            aggregate.apply(&event);
977        }
978        aggregate
979    }
980
981    /// Serialize current state for snapshotting.
982    ///
983    /// Default returns `None`: an aggregate opts out and is always reconstructed
984    /// by replaying its stream. Override to return `Some(state)` — typically via
985    /// `serde_json::to_value(self)` — once the aggregate is `Serialize`.
986    fn to_snapshot(&self) -> Option<serde_json::Value> {
987        None
988    }
989
990    /// Reconstruct an aggregate from a previously snapshotted state.
991    ///
992    /// Default returns `None` so a loader falls back to stream replay. Override
993    /// to deserialize the value produced by `to_snapshot`. `Option` (rather than
994    /// `Result`) keeps the contract simple: a `None` — whether opted out or a
995    /// failed decode — is always handled by replaying from sequence 0.
996    fn from_snapshot(state: serde_json::Value) -> Option<Self>
997    where
998        Self: Sized,
999    {
1000        let _ = state;
1001        None
1002    }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007    use super::*;
1008    use serde::{Deserialize, Serialize};
1009    use thiserror::Error;
1010
1011    // Test domain: Simple counter aggregate
1012    #[derive(Debug, Clone, Serialize, Deserialize)]
1013    enum CounterCommand {
1014        Create { id: String },
1015        Increment { id: String, amount: i32 },
1016        Decrement { id: String, amount: i32 },
1017    }
1018
1019    impl Command for CounterCommand {
1020        fn aggregate_id(&self) -> &str {
1021            match self {
1022                CounterCommand::Create { id } => id,
1023                CounterCommand::Increment { id, .. } => id,
1024                CounterCommand::Decrement { id, .. } => id,
1025            }
1026        }
1027    }
1028
1029    #[derive(Debug, Clone, Serialize, Deserialize)]
1030    enum CounterEvent {
1031        Created { id: String },
1032        Incremented { amount: i32 },
1033        Decremented { amount: i32 },
1034    }
1035
1036    #[derive(Debug, Error)]
1037    enum CounterError {
1038        #[error("Counter already exists")]
1039        AlreadyExists,
1040        #[error("Counter not found")]
1041        NotFound,
1042        #[error("Amount must be positive")]
1043        InvalidAmount,
1044        #[error("Counter would go negative")]
1045        WouldGoNegative,
1046    }
1047
1048    #[derive(Default)]
1049    struct CounterAggregate {
1050        id: Option<String>,
1051        value: i32,
1052        version: i64,
1053        created: bool,
1054    }
1055
1056    #[async_trait]
1057    impl Aggregate for CounterAggregate {
1058        type Command = CounterCommand;
1059        type Event = CounterEvent;
1060        type Error = CounterError;
1061
1062        fn aggregate_type() -> &'static str {
1063            "Counter"
1064        }
1065
1066        fn version(&self) -> i64 {
1067            self.version
1068        }
1069
1070        async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
1071            match command {
1072                CounterCommand::Create { id } => {
1073                    if self.created {
1074                        return Err(CounterError::AlreadyExists);
1075                    }
1076
1077                    Ok(vec![Event::new(NewEvent {
1078                        aggregate_type: "Counter",
1079                        aggregate_id: &id,
1080                        sequence: self.version + 1,
1081                        event_type: "Created",
1082                        payload: serde_json::json!({ "id": id }),
1083                    })])
1084                }
1085                CounterCommand::Increment { id, amount } => {
1086                    if !self.created {
1087                        return Err(CounterError::NotFound);
1088                    }
1089
1090                    if amount <= 0 {
1091                        return Err(CounterError::InvalidAmount);
1092                    }
1093
1094                    Ok(vec![Event::new(NewEvent {
1095                        aggregate_type: "Counter",
1096                        aggregate_id: &id,
1097                        sequence: self.version + 1,
1098                        event_type: "Incremented",
1099                        payload: serde_json::json!({ "amount": amount }),
1100                    })])
1101                }
1102                CounterCommand::Decrement { id, amount } => {
1103                    if !self.created {
1104                        return Err(CounterError::NotFound);
1105                    }
1106
1107                    if amount <= 0 {
1108                        return Err(CounterError::InvalidAmount);
1109                    }
1110
1111                    if self.value - amount < 0 {
1112                        return Err(CounterError::WouldGoNegative);
1113                    }
1114
1115                    Ok(vec![Event::new(NewEvent {
1116                        aggregate_type: "Counter",
1117                        aggregate_id: &id,
1118                        sequence: self.version + 1,
1119                        event_type: "Decremented",
1120                        payload: serde_json::json!({ "amount": amount }),
1121                    })])
1122                }
1123            }
1124        }
1125
1126        fn apply(&mut self, event: &Event) {
1127            self.version = event.sequence;
1128
1129            match event.event_type.as_str() {
1130                "Created" => {
1131                    self.id = Some(event.aggregate_id.clone());
1132                    self.value = 0;
1133                    self.created = true;
1134                }
1135                "Incremented" => {
1136                    let amount = event.payload["amount"].as_i64().unwrap() as i32;
1137                    self.value += amount;
1138                }
1139                "Decremented" => {
1140                    let amount = event.payload["amount"].as_i64().unwrap() as i32;
1141                    self.value -= amount;
1142                }
1143                _ => {}
1144            }
1145        }
1146    }
1147
1148    #[tokio::test]
1149    async fn test_command_aggregate_id() {
1150        let command = CounterCommand::Create {
1151            id: "counter-1".to_string(),
1152        };
1153
1154        assert_eq!(command.aggregate_id(), "counter-1");
1155    }
1156
1157    #[tokio::test]
1158    async fn test_aggregate_type() {
1159        assert_eq!(CounterAggregate::aggregate_type(), "Counter");
1160    }
1161
1162    #[tokio::test]
1163    async fn test_create_counter() {
1164        let aggregate = CounterAggregate::default();
1165
1166        let command = CounterCommand::Create {
1167            id: "counter-1".to_string(),
1168        };
1169
1170        let events = aggregate.handle(command).await.unwrap();
1171
1172        assert_eq!(events.len(), 1);
1173        assert_eq!(events[0].event_type, "Created");
1174        assert_eq!(events[0].aggregate_id, "counter-1");
1175        assert_eq!(events[0].sequence, 1);
1176    }
1177
1178    #[tokio::test]
1179    async fn test_cannot_create_twice() {
1180        let mut aggregate = CounterAggregate::default();
1181        let event = Event::new(NewEvent {
1182            aggregate_type: "Counter",
1183            aggregate_id: "counter-1",
1184            sequence: 1,
1185            event_type: "Created",
1186            payload: serde_json::json!({ "id": "counter-1" }),
1187        });
1188        aggregate.apply(&event);
1189
1190        let command = CounterCommand::Create {
1191            id: "counter-1".to_string(),
1192        };
1193
1194        let result = aggregate.handle(command).await;
1195        assert!(matches!(result, Err(CounterError::AlreadyExists)));
1196    }
1197
1198    #[tokio::test]
1199    async fn test_increment_counter() {
1200        let mut aggregate = CounterAggregate::default();
1201        let event = Event::new(NewEvent {
1202            aggregate_type: "Counter",
1203            aggregate_id: "counter-1",
1204            sequence: 1,
1205            event_type: "Created",
1206            payload: serde_json::json!({ "id": "counter-1" }),
1207        });
1208        aggregate.apply(&event);
1209
1210        let command = CounterCommand::Increment {
1211            id: "counter-1".to_string(),
1212            amount: 5,
1213        };
1214
1215        let events = aggregate.handle(command).await.unwrap();
1216        assert_eq!(events.len(), 1);
1217        assert_eq!(events[0].event_type, "Incremented");
1218        assert_eq!(events[0].sequence, 2);
1219    }
1220
1221    #[tokio::test]
1222    async fn test_increment_not_found() {
1223        let aggregate = CounterAggregate::default();
1224
1225        let command = CounterCommand::Increment {
1226            id: "counter-1".to_string(),
1227            amount: 5,
1228        };
1229
1230        let result = aggregate.handle(command).await;
1231        assert!(matches!(result, Err(CounterError::NotFound)));
1232    }
1233
1234    #[tokio::test]
1235    async fn test_decrement_counter() {
1236        let mut aggregate = CounterAggregate::default();
1237
1238        // Create and increment to 10
1239        let events = vec![
1240            Event::new(NewEvent {
1241                aggregate_type: "Counter",
1242                aggregate_id: "counter-1",
1243                sequence: 1,
1244                event_type: "Created",
1245                payload: serde_json::json!({ "id": "counter-1" }),
1246            }),
1247            Event::new(NewEvent {
1248                aggregate_type: "Counter",
1249                aggregate_id: "counter-1",
1250                sequence: 2,
1251                event_type: "Incremented",
1252                payload: serde_json::json!({ "amount": 10 }),
1253            }),
1254        ];
1255
1256        for event in events {
1257            aggregate.apply(&event);
1258        }
1259
1260        assert_eq!(aggregate.value, 10);
1261
1262        // Decrement by 3
1263        let command = CounterCommand::Decrement {
1264            id: "counter-1".to_string(),
1265            amount: 3,
1266        };
1267
1268        let events = aggregate.handle(command).await.unwrap();
1269        assert_eq!(events.len(), 1);
1270        assert_eq!(events[0].event_type, "Decremented");
1271    }
1272
1273    #[tokio::test]
1274    async fn test_decrement_would_go_negative() {
1275        let mut aggregate = CounterAggregate::default();
1276
1277        let events = vec![
1278            Event::new(NewEvent {
1279                aggregate_type: "Counter",
1280                aggregate_id: "counter-1",
1281                sequence: 1,
1282                event_type: "Created",
1283                payload: serde_json::json!({ "id": "counter-1" }),
1284            }),
1285            Event::new(NewEvent {
1286                aggregate_type: "Counter",
1287                aggregate_id: "counter-1",
1288                sequence: 2,
1289                event_type: "Incremented",
1290                payload: serde_json::json!({ "amount": 5 }),
1291            }),
1292        ];
1293
1294        for event in events {
1295            aggregate.apply(&event);
1296        }
1297
1298        assert_eq!(aggregate.value, 5);
1299
1300        // Try to decrement by 10 (would go negative)
1301        let command = CounterCommand::Decrement {
1302            id: "counter-1".to_string(),
1303            amount: 10,
1304        };
1305
1306        let result = aggregate.handle(command).await;
1307        assert!(matches!(result, Err(CounterError::WouldGoNegative)));
1308    }
1309
1310    #[tokio::test]
1311    async fn test_from_events() {
1312        let events = vec![
1313            Event::new(NewEvent {
1314                aggregate_type: "Counter",
1315                aggregate_id: "counter-1",
1316                sequence: 1,
1317                event_type: "Created",
1318                payload: serde_json::json!({ "id": "counter-1" }),
1319            }),
1320            Event::new(NewEvent {
1321                aggregate_type: "Counter",
1322                aggregate_id: "counter-1",
1323                sequence: 2,
1324                event_type: "Incremented",
1325                payload: serde_json::json!({ "amount": 5 }),
1326            }),
1327            Event::new(NewEvent {
1328                aggregate_type: "Counter",
1329                aggregate_id: "counter-1",
1330                sequence: 3,
1331                event_type: "Incremented",
1332                payload: serde_json::json!({ "amount": 3 }),
1333            }),
1334            Event::new(NewEvent {
1335                aggregate_type: "Counter",
1336                aggregate_id: "counter-1",
1337                sequence: 4,
1338                event_type: "Decremented",
1339                payload: serde_json::json!({ "amount": 2 }),
1340            }),
1341        ];
1342
1343        let aggregate = CounterAggregate::from_events(events);
1344
1345        assert_eq!(aggregate.version, 4);
1346        assert_eq!(aggregate.value, 6); // 0 + 5 + 3 - 2
1347        assert!(aggregate.created);
1348    }
1349
1350    #[tokio::test]
1351    async fn test_version_increments() {
1352        let aggregate = CounterAggregate::default();
1353        assert_eq!(aggregate.version(), 0);
1354
1355        let mut aggregate = CounterAggregate::default();
1356        let event1 = Event::new(NewEvent {
1357            aggregate_type: "Counter",
1358            aggregate_id: "counter-1",
1359            sequence: 1,
1360            event_type: "Created",
1361            payload: serde_json::json!({ "id": "counter-1" }),
1362        });
1363        aggregate.apply(&event1);
1364        assert_eq!(aggregate.version(), 1);
1365
1366        let event2 = Event::new(NewEvent {
1367            aggregate_type: "Counter",
1368            aggregate_id: "counter-1",
1369            sequence: 2,
1370            event_type: "Incremented",
1371            payload: serde_json::json!({ "amount": 5 }),
1372        });
1373        aggregate.apply(&event2);
1374        assert_eq!(aggregate.version(), 2);
1375    }
1376
1377    // An aggregate that opts into snapshots by serializing its own state.
1378    #[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
1379    struct SnapshotCounter {
1380        value: i32,
1381        version: i64,
1382    }
1383
1384    #[async_trait]
1385    impl Aggregate for SnapshotCounter {
1386        type Command = CounterCommand;
1387        type Event = CounterEvent;
1388        type Error = CounterError;
1389
1390        fn aggregate_type() -> &'static str {
1391            "SnapshotCounter"
1392        }
1393
1394        fn version(&self) -> i64 {
1395            self.version
1396        }
1397
1398        async fn handle(&self, _command: Self::Command) -> Result<Vec<Event>, Self::Error> {
1399            Ok(vec![])
1400        }
1401
1402        fn apply(&mut self, event: &Event) {
1403            self.version = event.sequence;
1404        }
1405
1406        fn to_snapshot(&self) -> Option<serde_json::Value> {
1407            serde_json::to_value(self).ok()
1408        }
1409
1410        fn from_snapshot(state: serde_json::Value) -> Option<Self> {
1411            serde_json::from_value(state).ok()
1412        }
1413    }
1414
1415    #[test]
1416    fn test_snapshot_round_trip_reconstructs_state() {
1417        let original = SnapshotCounter {
1418            value: 42,
1419            version: 7,
1420        };
1421        let state = original
1422            .to_snapshot()
1423            .expect("opted-in aggregate snapshots");
1424        let restored = SnapshotCounter::from_snapshot(state).expect("snapshot decodes");
1425        assert_eq!(original, restored);
1426    }
1427
1428    #[test]
1429    fn test_default_snapshot_methods_opt_out() {
1430        // CounterAggregate does not override the snapshot hooks.
1431        assert!(CounterAggregate::default().to_snapshot().is_none());
1432        assert!(CounterAggregate::from_snapshot(serde_json::json!({})).is_none());
1433    }
1434
1435    #[tokio::test]
1436    async fn test_apply_unknown_event() {
1437        let mut aggregate = CounterAggregate::default();
1438
1439        // Unknown event type should be silently ignored
1440        let event = Event::new(NewEvent {
1441            aggregate_type: "Counter",
1442            aggregate_id: "counter-1",
1443            sequence: 1,
1444            event_type: "UnknownEvent",
1445            payload: serde_json::json!({}),
1446        });
1447
1448        aggregate.apply(&event);
1449
1450        // Version still updates (this is important for correctness)
1451        assert_eq!(aggregate.version, 1);
1452        // But state is unchanged
1453        assert!(!aggregate.created);
1454        assert_eq!(aggregate.value, 0);
1455    }
1456}