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;
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(
191//!                     "User",
192//!                     &id,
193//!                     self.version + 1,
194//!                     "UserCreated",
195//!                     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(
208//!                     "User",
209//!                     &id,
210//!                     self.version + 1,
211//!                     "ProfileUpdated",
212//!                     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(
225//!                     "User",
226//!                     &id,
227//!                     self.version + 1,
228//!                     "EmailChanged",
229//!                     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;
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;
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("User", &id, self.version + 1, "UserCreated",
403//! #                     serde_json::json!({ "id": id, "name": name, "email": email }))])
404//! #             }
405//! #         }
406//! #     }
407//! #     fn apply(&mut self, event: &Event) {
408//! #         self.version = event.sequence;
409//! #         if event.event_type == "UserCreated" {
410//! #             self.id = Some(event.aggregate_id.clone());
411//! #             self.created = true;
412//! #         }
413//! #     }
414//! # }
415//! #
416//! #[tokio::test]
417//! async fn test_user_creation() {
418//!     // Given: A new user aggregate
419//!     let aggregate = UserAggregate::default();
420//!
421//!     // When: Creating a user
422//!     let command = UserCommand::CreateUser {
423//!         id: "user-123".to_string(),
424//!         name: "Alice".to_string(),
425//!         email: "alice@example.com".to_string(),
426//!     };
427//!
428//!     let events = aggregate.handle(command).await.unwrap();
429//!
430//!     // Then: UserCreated event is produced
431//!     assert_eq!(events.len(), 1);
432//!     assert_eq!(events[0].event_type, "UserCreated");
433//!     assert_eq!(events[0].aggregate_id, "user-123");
434//! }
435//!
436//! #[tokio::test]
437//! async fn test_invalid_email() {
438//!     // Given: A new user aggregate
439//!     let aggregate = UserAggregate::default();
440//!
441//!     // When: Creating a user with invalid email
442//!     let command = UserCommand::CreateUser {
443//!         id: "user-456".to_string(),
444//!         name: "Bob".to_string(),
445//!         email: "not-an-email".to_string(), // Invalid!
446//!     };
447//!
448//!     let result = aggregate.handle(command).await;
449//!
450//!     // Then: Error is returned
451//!     assert!(result.is_err());
452//! }
453//!
454//! #[tokio::test]
455//! async fn test_cannot_create_twice() {
456//!     // Given: An existing user (reconstructed from events)
457//!     let mut aggregate = UserAggregate::default();
458//!     let event = Event::new(
459//!         "User",
460//!         "user-789",
461//!         1,
462//!         "UserCreated",
463//!         serde_json::json!({
464//!             "id": "user-789",
465//!             "name": "Charlie",
466//!             "email": "charlie@example.com"
467//!         }),
468//!     );
469//!     aggregate.apply(&event);
470//!
471//!     // When: Trying to create the user again
472//!     let command = UserCommand::CreateUser {
473//!         id: "user-789".to_string(),
474//!         name: "Charlie".to_string(),
475//!         email: "charlie@example.com".to_string(),
476//!     };
477//!
478//!     let result = aggregate.handle(command).await;
479//!
480//!     // Then: Error is returned
481//!     assert!(result.is_err());
482//! }
483//! ```
484//!
485//! ## Advanced Patterns
486//!
487//! ### Multiple Events from One Command
488//!
489//! Sometimes a command should produce multiple events atomically:
490//!
491//! ```rust,ignore
492//! async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
493//!     match command {
494//!         OrderCommand::PlaceOrder { order_id, items } => {
495//!             // Validate stock
496//!             // ...
497//!
498//!             // Produce multiple events
499//!             Ok(vec![
500//!                 Event::new("Order", &order_id, self.version + 1, "OrderPlaced", ...),
501//!                 Event::new("Order", &order_id, self.version + 2, "InventoryReserved", ...),
502//!                 Event::new("Order", &order_id, self.version + 3, "PaymentRequested", ...),
503//!             ])
504//!         }
505//!     }
506//! }
507//! ```
508//!
509//! ### Conditional Events
510//!
511//! Commands may produce zero events if preconditions aren't met:
512//!
513//! ```rust,ignore
514//! async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
515//!     match command {
516//!         UserCommand::MarkAsActive { id } => {
517//!             // If already active, no event needed
518//!             if self.is_active {
519//!                 return Ok(vec![]);
520//!             }
521//!
522//!             Ok(vec![Event::new("User", &id, self.version + 1, "UserActivated", ...)])
523//!         }
524//!     }
525//! }
526//! ```
527//!
528//! ### Complex Validation
529//!
530//! Use the aggregate state to enforce complex business rules:
531//!
532//! ```rust,ignore
533//! async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
534//!     match command {
535//!         AccountCommand::Withdraw { amount } => {
536//!             // Check balance
537//!             if self.balance < amount {
538//!                 return Err(AccountError::InsufficientFunds);
539//!             }
540//!
541//!             // Check withdrawal limit
542//!             if self.daily_withdrawals + amount > self.daily_limit {
543//!                 return Err(AccountError::DailyLimitExceeded);
544//!             }
545//!
546//!             Ok(vec![Event::new("Account", &self.id, self.version + 1, "Withdrawn", ...)])
547//!         }
548//!     }
549//! }
550//! ```
551
552use crate::event::Event;
553use async_trait::async_trait;
554use std::error::Error;
555
556/// Trait for commands that can be dispatched to aggregates.
557///
558/// Commands represent intent to change aggregate state. They are imperative
559/// (e.g., `CreateUser`, `UpdateProfile`) and can be rejected if business rules
560/// aren't satisfied.
561///
562/// # Design Principles
563///
564/// - **Imperative naming**: Use verbs (Create, Update, Delete, Activate)
565/// - **Validation happens in aggregates**: Commands may be rejected
566/// - **Identify target**: Commands must know which aggregate instance to operate on
567/// - **Serializable**: Commands should be serializable for command sourcing
568///
569/// # Example
570///
571/// ```rust
572/// use arc_core::aggregate::Command;
573/// use serde::{Deserialize, Serialize};
574///
575/// #[derive(Debug, Clone, Serialize, Deserialize)]
576/// pub enum UserCommand {
577///     CreateUser { id: String, name: String, email: String },
578///     UpdateProfile { id: String, name: String },
579///     DeleteUser { id: String },
580/// }
581///
582/// impl Command for UserCommand {
583///     fn aggregate_id(&self) -> &str {
584///         match self {
585///             UserCommand::CreateUser { id, .. } => id,
586///             UserCommand::UpdateProfile { id, .. } => id,
587///             UserCommand::DeleteUser { id } => id,
588///         }
589///     }
590/// }
591/// ```
592pub trait Command: Send + Sync {
593    /// Get the aggregate instance ID this command targets.
594    ///
595    /// The command bus uses this to load the correct aggregate instance
596    /// from the event store.
597    ///
598    /// # Example
599    ///
600    /// ```rust
601    /// # use arc_core::aggregate::Command;
602    /// # use serde::{Deserialize, Serialize};
603    /// #
604    /// # #[derive(Debug, Clone, Serialize, Deserialize)]
605    /// # pub enum UserCommand {
606    /// #     CreateUser { id: String, name: String },
607    /// # }
608    /// #
609    /// # impl Command for UserCommand {
610    /// #     fn aggregate_id(&self) -> &str {
611    /// #         match self {
612    /// #             UserCommand::CreateUser { id, .. } => id,
613    /// #         }
614    /// #     }
615    /// # }
616    /// #
617    /// let command = UserCommand::CreateUser {
618    ///     id: "user-123".to_string(),
619    ///     name: "Alice".to_string(),
620    /// };
621    ///
622    /// assert_eq!(command.aggregate_id(), "user-123");
623    /// ```
624    fn aggregate_id(&self) -> &str;
625}
626
627/// Trait for domain aggregates in event sourcing.
628///
629/// Aggregates are consistency boundaries that encapsulate business logic,
630/// enforce invariants, and produce events in response to commands.
631///
632/// # Lifecycle
633///
634/// 1. **Load**: Reconstruct aggregate from event stream using `from_events()`
635/// 2. **Command**: Handle command with `handle()` to produce new events
636/// 3. **Store**: Persist events to event store (done by command bus)
637/// 4. **Apply**: Apply new events to update aggregate state using `apply()`
638/// 5. **Publish**: Publish events to event bus (done by command bus)
639///
640/// # Design Principles
641///
642/// - **State is private**: Aggregate state should not be exposed outside
643/// - **Commands produce events**: `handle()` validates and produces events
644/// - **Events update state**: `apply()` updates internal state deterministically
645/// - **Pure functions**: `handle()` has no side effects (no I/O, no mutations)
646/// - **Deterministic apply**: Same events always produce same state
647/// - **Default implementation**: Aggregate must implement `Default` for initial state
648///
649/// # Type Parameters
650///
651/// - `Command`: The command type this aggregate handles (must implement `Command`)
652/// - `Event`: The domain event type (typically an enum, must be serializable)
653/// - `Error`: The error type for validation failures (must implement `std::error::Error`)
654///
655/// # Example
656///
657/// See the module-level documentation for a complete example.
658#[async_trait]
659pub trait Aggregate: Send + Sync + Default {
660    /// The command type this aggregate handles
661    type Command: Command;
662
663    /// The domain event type (your custom enum)
664    type Event;
665
666    /// The error type for validation failures
667    type Error: Error + Send + Sync + 'static;
668
669    /// Get the aggregate type name (e.g., "User", "Order", "Account").
670    ///
671    /// This is used for event metadata and debugging. Should be a static string
672    /// that uniquely identifies this aggregate type in your domain.
673    ///
674    /// # Example
675    ///
676    /// ```rust
677    /// # use arc_core::aggregate::{Aggregate, Command};
678    /// # use arc_core::event::Event;
679    /// # use thiserror::Error;
680    /// #
681    /// # #[derive(Debug)]
682    /// # struct DummyCommand;
683    /// # impl Command for DummyCommand {
684    /// #     fn aggregate_id(&self) -> &str { "dummy" }
685    /// # }
686    /// #
687    /// # #[derive(Debug, Error)]
688    /// # #[error("dummy error")]
689    /// # struct DummyError;
690    /// #
691    /// # #[derive(Default)]
692    /// # struct UserAggregate;
693    /// #
694    /// # #[async_trait::async_trait]
695    /// # impl Aggregate for UserAggregate {
696    /// #     type Command = DummyCommand;
697    /// #     type Event = ();
698    /// #     type Error = DummyError;
699    /// #
700    /// fn aggregate_type() -> &'static str {
701    ///     "User"
702    /// }
703    /// #
704    /// #     fn version(&self) -> i64 { 0 }
705    /// #     async fn handle(&self, _: Self::Command) -> Result<Vec<Event>, Self::Error> { Ok(vec![]) }
706    /// #     fn apply(&mut self, _: &Event) {}
707    /// # }
708    /// #
709    /// assert_eq!(UserAggregate::aggregate_type(), "User");
710    /// ```
711    fn aggregate_type() -> &'static str;
712
713    /// Get the current version (sequence number) of this aggregate.
714    ///
715    /// The version represents how many events have been applied to this aggregate.
716    /// It starts at 0 for a new aggregate and increments with each event.
717    ///
718    /// This is used for optimistic concurrency control - the event store checks
719    /// that the version hasn't changed since the aggregate was loaded.
720    ///
721    /// # Example
722    ///
723    /// ```rust
724    /// # use arc_core::aggregate::{Aggregate, Command};
725    /// # use arc_core::event::Event;
726    /// # use thiserror::Error;
727    /// #
728    /// # #[derive(Debug)]
729    /// # struct DummyCommand;
730    /// # impl Command for DummyCommand {
731    /// #     fn aggregate_id(&self) -> &str { "dummy" }
732    /// # }
733    /// #
734    /// # #[derive(Debug, Error)]
735    /// # #[error("dummy error")]
736    /// # struct DummyError;
737    /// #
738    /// # #[derive(Default)]
739    /// # struct UserAggregate { version: i64 }
740    /// #
741    /// # #[async_trait::async_trait]
742    /// # impl Aggregate for UserAggregate {
743    /// #     type Command = DummyCommand;
744    /// #     type Event = ();
745    /// #     type Error = DummyError;
746    /// #     fn aggregate_type() -> &'static str { "User" }
747    /// #
748    /// fn version(&self) -> i64 {
749    ///     self.version
750    /// }
751    /// #
752    /// #     async fn handle(&self, _: Self::Command) -> Result<Vec<Event>, Self::Error> { Ok(vec![]) }
753    /// #     fn apply(&mut self, _: &Event) {}
754    /// # }
755    /// #
756    /// let aggregate = UserAggregate::default();
757    /// assert_eq!(aggregate.version(), 0); // New aggregate
758    /// ```
759    fn version(&self) -> i64;
760
761    /// Handle a command and produce events.
762    ///
763    /// This is where your business logic lives. The method should:
764    ///
765    /// 1. Inspect current state (`self`) to make decisions
766    /// 2. Validate the command against business rules
767    /// 3. Return error if validation fails
768    /// 4. Produce one or more events if validation succeeds
769    /// 5. Return empty vec if command has no effect
770    ///
771    /// **Important**: This method should have no side effects:
772    /// - Don't write to databases
773    /// - Don't call external APIs
774    /// - Don't mutate state
775    ///
776    /// Side effects happen in projections and event handlers.
777    ///
778    /// # Arguments
779    ///
780    /// - `command`: The command to handle
781    ///
782    /// # Returns
783    ///
784    /// - `Ok(Vec<Event>)`: One or more events to persist and publish
785    /// - `Err(Self::Error)`: Validation or business rule failure
786    ///
787    /// # Example
788    ///
789    /// ```rust,ignore
790    /// async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
791    ///     match command {
792    ///         UserCommand::CreateUser { id, name, email } => {
793    ///             // Check invariant
794    ///             if self.created {
795    ///                 return Err(UserError::AlreadyExists);
796    ///             }
797    ///
798    ///             // Validate input
799    ///             if !email.contains('@') {
800    ///                 return Err(UserError::InvalidEmail);
801    ///             }
802    ///
803    ///             // Produce event
804    ///             Ok(vec![Event::new("User", &id, self.version + 1, "UserCreated", ...)])
805    ///         }
806    ///     }
807    /// }
808    /// ```
809    async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error>;
810
811    /// Apply an event to update aggregate state.
812    ///
813    /// This method must be **deterministic** and **side-effect free**:
814    /// - Same events always produce same state
815    /// - No I/O operations
816    /// - No randomness
817    /// - No external dependencies
818    ///
819    /// The event is already persisted when this is called. Your job is to
820    /// update the aggregate's internal state to reflect the event.
821    ///
822    /// # Arguments
823    ///
824    /// - `event`: The event to apply (already persisted)
825    ///
826    /// # Example
827    ///
828    /// ```rust,ignore
829    /// fn apply(&mut self, event: &Event) {
830    ///     self.version = event.sequence;
831    ///
832    ///     match event.event_type.as_str() {
833    ///         "UserCreated" => {
834    ///             self.id = Some(event.payload["id"].as_str().unwrap().to_string());
835    ///             self.name = Some(event.payload["name"].as_str().unwrap().to_string());
836    ///             self.created = true;
837    ///         }
838    ///         "ProfileUpdated" => {
839    ///             self.name = Some(event.payload["name"].as_str().unwrap().to_string());
840    ///         }
841    ///         _ => {} // Unknown event types are ignored
842    ///     }
843    /// }
844    /// ```
845    fn apply(&mut self, event: &Event);
846
847    /// Reconstruct aggregate from its event stream.
848    ///
849    /// This method has a default implementation that:
850    /// 1. Creates a new aggregate instance using `Default::default()`
851    /// 2. Applies each event in order using `apply()`
852    /// 3. Returns the reconstructed aggregate
853    ///
854    /// You typically don't need to override this unless you have special requirements.
855    ///
856    /// # Arguments
857    ///
858    /// - `events`: The complete event stream for this aggregate
859    ///
860    /// # Returns
861    ///
862    /// A fully reconstructed aggregate with all events applied
863    ///
864    /// # Example
865    ///
866    /// ```rust
867    /// # use arc_core::aggregate::{Aggregate, Command};
868    /// # use arc_core::event::Event;
869    /// # use thiserror::Error;
870    /// #
871    /// # #[derive(Debug)]
872    /// # struct DummyCommand;
873    /// # impl Command for DummyCommand {
874    /// #     fn aggregate_id(&self) -> &str { "dummy" }
875    /// # }
876    /// #
877    /// # #[derive(Debug, Error)]
878    /// # #[error("dummy error")]
879    /// # struct DummyError;
880    /// #
881    /// # #[derive(Default)]
882    /// # struct UserAggregate {
883    /// #     id: Option<String>,
884    /// #     version: i64,
885    /// #     created: bool,
886    /// # }
887    /// #
888    /// # #[async_trait::async_trait]
889    /// # impl Aggregate for UserAggregate {
890    /// #     type Command = DummyCommand;
891    /// #     type Event = ();
892    /// #     type Error = DummyError;
893    /// #     fn aggregate_type() -> &'static str { "User" }
894    /// #     fn version(&self) -> i64 { self.version }
895    /// #     async fn handle(&self, _: Self::Command) -> Result<Vec<Event>, Self::Error> { Ok(vec![]) }
896    /// #
897    /// #     fn apply(&mut self, event: &Event) {
898    /// #         self.version = event.sequence;
899    /// #         if event.event_type == "UserCreated" {
900    /// #             self.id = Some(event.aggregate_id.clone());
901    /// #             self.created = true;
902    /// #         }
903    /// #     }
904    /// # }
905    /// #
906    /// // Load events from event store
907    /// let events = vec![
908    ///     Event::new("User", "user-123", 1, "UserCreated", serde_json::json!({"id": "user-123"})),
909    ///     Event::new("User", "user-123", 2, "ProfileUpdated", serde_json::json!({"name": "Alice"})),
910    /// ];
911    ///
912    /// // Reconstruct aggregate
913    /// let aggregate = UserAggregate::from_events(events);
914    ///
915    /// assert_eq!(aggregate.version(), 2);
916    /// assert!(aggregate.created);
917    /// ```
918    fn from_events(events: Vec<Event>) -> Self {
919        let mut aggregate = Self::default();
920        for event in events {
921            aggregate.apply(&event);
922        }
923        aggregate
924    }
925
926    /// Serialize current state for snapshotting.
927    ///
928    /// Default returns `None`: an aggregate opts out and is always reconstructed
929    /// by replaying its stream. Override to return `Some(state)` — typically via
930    /// `serde_json::to_value(self)` — once the aggregate is `Serialize`.
931    fn to_snapshot(&self) -> Option<serde_json::Value> {
932        None
933    }
934
935    /// Reconstruct an aggregate from a previously snapshotted state.
936    ///
937    /// Default returns `None` so a loader falls back to stream replay. Override
938    /// to deserialize the value produced by `to_snapshot`. `Option` (rather than
939    /// `Result`) keeps the contract simple: a `None` — whether opted out or a
940    /// failed decode — is always handled by replaying from sequence 0.
941    fn from_snapshot(state: serde_json::Value) -> Option<Self>
942    where
943        Self: Sized,
944    {
945        let _ = state;
946        None
947    }
948}
949
950#[cfg(test)]
951mod tests {
952    use super::*;
953    use serde::{Deserialize, Serialize};
954    use thiserror::Error;
955
956    // Test domain: Simple counter aggregate
957    #[derive(Debug, Clone, Serialize, Deserialize)]
958    enum CounterCommand {
959        Create { id: String },
960        Increment { id: String, amount: i32 },
961        Decrement { id: String, amount: i32 },
962    }
963
964    impl Command for CounterCommand {
965        fn aggregate_id(&self) -> &str {
966            match self {
967                CounterCommand::Create { id } => id,
968                CounterCommand::Increment { id, .. } => id,
969                CounterCommand::Decrement { id, .. } => id,
970            }
971        }
972    }
973
974    #[derive(Debug, Clone, Serialize, Deserialize)]
975    enum CounterEvent {
976        Created { id: String },
977        Incremented { amount: i32 },
978        Decremented { amount: i32 },
979    }
980
981    #[derive(Debug, Error)]
982    enum CounterError {
983        #[error("Counter already exists")]
984        AlreadyExists,
985        #[error("Counter not found")]
986        NotFound,
987        #[error("Amount must be positive")]
988        InvalidAmount,
989        #[error("Counter would go negative")]
990        WouldGoNegative,
991    }
992
993    #[derive(Default)]
994    struct CounterAggregate {
995        id: Option<String>,
996        value: i32,
997        version: i64,
998        created: bool,
999    }
1000
1001    #[async_trait]
1002    impl Aggregate for CounterAggregate {
1003        type Command = CounterCommand;
1004        type Event = CounterEvent;
1005        type Error = CounterError;
1006
1007        fn aggregate_type() -> &'static str {
1008            "Counter"
1009        }
1010
1011        fn version(&self) -> i64 {
1012            self.version
1013        }
1014
1015        async fn handle(&self, command: Self::Command) -> Result<Vec<Event>, Self::Error> {
1016            match command {
1017                CounterCommand::Create { id } => {
1018                    if self.created {
1019                        return Err(CounterError::AlreadyExists);
1020                    }
1021
1022                    Ok(vec![Event::new(
1023                        "Counter",
1024                        &id,
1025                        self.version + 1,
1026                        "Created",
1027                        serde_json::json!({ "id": id }),
1028                    )])
1029                }
1030                CounterCommand::Increment { id, amount } => {
1031                    if !self.created {
1032                        return Err(CounterError::NotFound);
1033                    }
1034
1035                    if amount <= 0 {
1036                        return Err(CounterError::InvalidAmount);
1037                    }
1038
1039                    Ok(vec![Event::new(
1040                        "Counter",
1041                        &id,
1042                        self.version + 1,
1043                        "Incremented",
1044                        serde_json::json!({ "amount": amount }),
1045                    )])
1046                }
1047                CounterCommand::Decrement { id, amount } => {
1048                    if !self.created {
1049                        return Err(CounterError::NotFound);
1050                    }
1051
1052                    if amount <= 0 {
1053                        return Err(CounterError::InvalidAmount);
1054                    }
1055
1056                    if self.value - amount < 0 {
1057                        return Err(CounterError::WouldGoNegative);
1058                    }
1059
1060                    Ok(vec![Event::new(
1061                        "Counter",
1062                        &id,
1063                        self.version + 1,
1064                        "Decremented",
1065                        serde_json::json!({ "amount": amount }),
1066                    )])
1067                }
1068            }
1069        }
1070
1071        fn apply(&mut self, event: &Event) {
1072            self.version = event.sequence;
1073
1074            match event.event_type.as_str() {
1075                "Created" => {
1076                    self.id = Some(event.aggregate_id.clone());
1077                    self.value = 0;
1078                    self.created = true;
1079                }
1080                "Incremented" => {
1081                    let amount = event.payload["amount"].as_i64().unwrap() as i32;
1082                    self.value += amount;
1083                }
1084                "Decremented" => {
1085                    let amount = event.payload["amount"].as_i64().unwrap() as i32;
1086                    self.value -= amount;
1087                }
1088                _ => {}
1089            }
1090        }
1091    }
1092
1093    #[tokio::test]
1094    async fn test_command_aggregate_id() {
1095        let command = CounterCommand::Create {
1096            id: "counter-1".to_string(),
1097        };
1098
1099        assert_eq!(command.aggregate_id(), "counter-1");
1100    }
1101
1102    #[tokio::test]
1103    async fn test_aggregate_type() {
1104        assert_eq!(CounterAggregate::aggregate_type(), "Counter");
1105    }
1106
1107    #[tokio::test]
1108    async fn test_create_counter() {
1109        let aggregate = CounterAggregate::default();
1110
1111        let command = CounterCommand::Create {
1112            id: "counter-1".to_string(),
1113        };
1114
1115        let events = aggregate.handle(command).await.unwrap();
1116
1117        assert_eq!(events.len(), 1);
1118        assert_eq!(events[0].event_type, "Created");
1119        assert_eq!(events[0].aggregate_id, "counter-1");
1120        assert_eq!(events[0].sequence, 1);
1121    }
1122
1123    #[tokio::test]
1124    async fn test_cannot_create_twice() {
1125        let mut aggregate = CounterAggregate::default();
1126        let event = Event::new(
1127            "Counter",
1128            "counter-1",
1129            1,
1130            "Created",
1131            serde_json::json!({ "id": "counter-1" }),
1132        );
1133        aggregate.apply(&event);
1134
1135        let command = CounterCommand::Create {
1136            id: "counter-1".to_string(),
1137        };
1138
1139        let result = aggregate.handle(command).await;
1140        assert!(matches!(result, Err(CounterError::AlreadyExists)));
1141    }
1142
1143    #[tokio::test]
1144    async fn test_increment_counter() {
1145        let mut aggregate = CounterAggregate::default();
1146        let event = Event::new(
1147            "Counter",
1148            "counter-1",
1149            1,
1150            "Created",
1151            serde_json::json!({ "id": "counter-1" }),
1152        );
1153        aggregate.apply(&event);
1154
1155        let command = CounterCommand::Increment {
1156            id: "counter-1".to_string(),
1157            amount: 5,
1158        };
1159
1160        let events = aggregate.handle(command).await.unwrap();
1161        assert_eq!(events.len(), 1);
1162        assert_eq!(events[0].event_type, "Incremented");
1163        assert_eq!(events[0].sequence, 2);
1164    }
1165
1166    #[tokio::test]
1167    async fn test_increment_not_found() {
1168        let aggregate = CounterAggregate::default();
1169
1170        let command = CounterCommand::Increment {
1171            id: "counter-1".to_string(),
1172            amount: 5,
1173        };
1174
1175        let result = aggregate.handle(command).await;
1176        assert!(matches!(result, Err(CounterError::NotFound)));
1177    }
1178
1179    #[tokio::test]
1180    async fn test_decrement_counter() {
1181        let mut aggregate = CounterAggregate::default();
1182
1183        // Create and increment to 10
1184        let events = vec![
1185            Event::new(
1186                "Counter",
1187                "counter-1",
1188                1,
1189                "Created",
1190                serde_json::json!({ "id": "counter-1" }),
1191            ),
1192            Event::new(
1193                "Counter",
1194                "counter-1",
1195                2,
1196                "Incremented",
1197                serde_json::json!({ "amount": 10 }),
1198            ),
1199        ];
1200
1201        for event in events {
1202            aggregate.apply(&event);
1203        }
1204
1205        assert_eq!(aggregate.value, 10);
1206
1207        // Decrement by 3
1208        let command = CounterCommand::Decrement {
1209            id: "counter-1".to_string(),
1210            amount: 3,
1211        };
1212
1213        let events = aggregate.handle(command).await.unwrap();
1214        assert_eq!(events.len(), 1);
1215        assert_eq!(events[0].event_type, "Decremented");
1216    }
1217
1218    #[tokio::test]
1219    async fn test_decrement_would_go_negative() {
1220        let mut aggregate = CounterAggregate::default();
1221
1222        let events = vec![
1223            Event::new(
1224                "Counter",
1225                "counter-1",
1226                1,
1227                "Created",
1228                serde_json::json!({ "id": "counter-1" }),
1229            ),
1230            Event::new(
1231                "Counter",
1232                "counter-1",
1233                2,
1234                "Incremented",
1235                serde_json::json!({ "amount": 5 }),
1236            ),
1237        ];
1238
1239        for event in events {
1240            aggregate.apply(&event);
1241        }
1242
1243        assert_eq!(aggregate.value, 5);
1244
1245        // Try to decrement by 10 (would go negative)
1246        let command = CounterCommand::Decrement {
1247            id: "counter-1".to_string(),
1248            amount: 10,
1249        };
1250
1251        let result = aggregate.handle(command).await;
1252        assert!(matches!(result, Err(CounterError::WouldGoNegative)));
1253    }
1254
1255    #[tokio::test]
1256    async fn test_from_events() {
1257        let events = vec![
1258            Event::new(
1259                "Counter",
1260                "counter-1",
1261                1,
1262                "Created",
1263                serde_json::json!({ "id": "counter-1" }),
1264            ),
1265            Event::new(
1266                "Counter",
1267                "counter-1",
1268                2,
1269                "Incremented",
1270                serde_json::json!({ "amount": 5 }),
1271            ),
1272            Event::new(
1273                "Counter",
1274                "counter-1",
1275                3,
1276                "Incremented",
1277                serde_json::json!({ "amount": 3 }),
1278            ),
1279            Event::new(
1280                "Counter",
1281                "counter-1",
1282                4,
1283                "Decremented",
1284                serde_json::json!({ "amount": 2 }),
1285            ),
1286        ];
1287
1288        let aggregate = CounterAggregate::from_events(events);
1289
1290        assert_eq!(aggregate.version, 4);
1291        assert_eq!(aggregate.value, 6); // 0 + 5 + 3 - 2
1292        assert!(aggregate.created);
1293    }
1294
1295    #[tokio::test]
1296    async fn test_version_increments() {
1297        let aggregate = CounterAggregate::default();
1298        assert_eq!(aggregate.version(), 0);
1299
1300        let mut aggregate = CounterAggregate::default();
1301        let event1 = Event::new(
1302            "Counter",
1303            "counter-1",
1304            1,
1305            "Created",
1306            serde_json::json!({ "id": "counter-1" }),
1307        );
1308        aggregate.apply(&event1);
1309        assert_eq!(aggregate.version(), 1);
1310
1311        let event2 = Event::new(
1312            "Counter",
1313            "counter-1",
1314            2,
1315            "Incremented",
1316            serde_json::json!({ "amount": 5 }),
1317        );
1318        aggregate.apply(&event2);
1319        assert_eq!(aggregate.version(), 2);
1320    }
1321
1322    // An aggregate that opts into snapshots by serializing its own state.
1323    #[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
1324    struct SnapshotCounter {
1325        value: i32,
1326        version: i64,
1327    }
1328
1329    #[async_trait]
1330    impl Aggregate for SnapshotCounter {
1331        type Command = CounterCommand;
1332        type Event = CounterEvent;
1333        type Error = CounterError;
1334
1335        fn aggregate_type() -> &'static str {
1336            "SnapshotCounter"
1337        }
1338
1339        fn version(&self) -> i64 {
1340            self.version
1341        }
1342
1343        async fn handle(&self, _command: Self::Command) -> Result<Vec<Event>, Self::Error> {
1344            Ok(vec![])
1345        }
1346
1347        fn apply(&mut self, event: &Event) {
1348            self.version = event.sequence;
1349        }
1350
1351        fn to_snapshot(&self) -> Option<serde_json::Value> {
1352            serde_json::to_value(self).ok()
1353        }
1354
1355        fn from_snapshot(state: serde_json::Value) -> Option<Self> {
1356            serde_json::from_value(state).ok()
1357        }
1358    }
1359
1360    #[test]
1361    fn test_snapshot_round_trip_reconstructs_state() {
1362        let original = SnapshotCounter {
1363            value: 42,
1364            version: 7,
1365        };
1366        let state = original
1367            .to_snapshot()
1368            .expect("opted-in aggregate snapshots");
1369        let restored = SnapshotCounter::from_snapshot(state).expect("snapshot decodes");
1370        assert_eq!(original, restored);
1371    }
1372
1373    #[test]
1374    fn test_default_snapshot_methods_opt_out() {
1375        // CounterAggregate does not override the snapshot hooks.
1376        assert!(CounterAggregate::default().to_snapshot().is_none());
1377        assert!(CounterAggregate::from_snapshot(serde_json::json!({})).is_none());
1378    }
1379
1380    #[tokio::test]
1381    async fn test_apply_unknown_event() {
1382        let mut aggregate = CounterAggregate::default();
1383
1384        // Unknown event type should be silently ignored
1385        let event = Event::new(
1386            "Counter",
1387            "counter-1",
1388            1,
1389            "UnknownEvent",
1390            serde_json::json!({}),
1391        );
1392
1393        aggregate.apply(&event);
1394
1395        // Version still updates (this is important for correctness)
1396        assert_eq!(aggregate.version, 1);
1397        // But state is unchanged
1398        assert!(!aggregate.created);
1399        assert_eq!(aggregate.value, 0);
1400    }
1401}