use ddd_cqrs_es::Aggregate;
impl Aggregate for BankAccount {
type Id = String;
type Command = BankAccountCommand;
type Event = BankAccountEvent;
type Error = BankAccountError;
/// A unique name for this type of aggregate, used to namespace streams in databases.
fn aggregate_type() -> &'static str {
"bank_account"
}
/// Current version number of the aggregate (tracks replayed events count).
fn revision(&self) -> u64 {
self.revision
}
/// Factory method to initialize an empty aggregate prior to state replay.
fn new() -> Self {
Self::default()
}
/// Replays past historical events to rebuild state in-memory.
/// This method MUST be completely deterministic and free of side effects.
fn apply(&mut self, event: &Self::Event) {
match event {
BankAccountEvent::AccountOpened { account_id, owner } => {
self.id = Some(account_id.clone());
self.owner = Some(owner.clone());
self.balance = 0;
}
BankAccountEvent::MoneyDeposited { amount } => {
self.balance += amount;
}
BankAccountEvent::MoneyWithdrawn { amount } => {
self.balance -= amount;
}
}
// Increment the revision counter to track stream version
self.revision += 1;
}
/// Handles incoming commands against the current replayed state.
/// Validates business invariants and returns new events or an error.
/// It must NOT mutate state directly (state is only mutated in apply()).
fn handle(&self, command: Self::Command) -> Result<Vec<Self::Event>, Self::Error> {
match command {
BankAccountCommand::OpenAccount { account_id, owner } => {
// Invariant: Cannot open an account that is already active
if self.id.is_some() {
return Err(BankAccountError::AccountAlreadyOpen);
}
Ok(vec![BankAccountEvent::AccountOpened { account_id, owner }])
}
BankAccountCommand::DepositMoney { amount } => {
// Invariant: Cannot deposit into a non-existent account
if self.id.is_none() {
return Err(BankAccountError::AccountNotYetOpen);
}
// Invariant: Deposit must be positive
if amount == 0 {
return Err(BankAccountError::InvalidDepositAmount);
}
Ok(vec![BankAccountEvent::MoneyDeposited { amount }])
}
BankAccountCommand::WithdrawMoney { amount } => {
// Invariant: Cannot withdraw from a non-existent account
if self.id.is_none() {
return Err(BankAccountError::AccountNotYetOpen);
}
// Invariant: Cannot withdraw more than the available balance
if self.balance < amount {
return Err(BankAccountError::InsufficientFunds {
available: self.balance,
requested: amount,
});
}
Ok(vec![BankAccountEvent::MoneyWithdrawn { amount }])
}
}
}
}