use eventastic::aggregate::Aggregate;
use eventastic::aggregate::SideEffect;
use eventastic::event::DomainEvent;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct Account {
pub account_id: Uuid,
pub balance: i64,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum AccountEvent {
Open {
account_id: Uuid,
event_id: Uuid,
email: String,
starting_balance: i64,
},
Add {
event_id: Uuid,
amount: i64,
},
Remove {
event_id: Uuid,
amount: i64,
},
}
impl DomainEvent for AccountEvent {
type EventId = Uuid;
fn id(&self) -> &Uuid {
match self {
AccountEvent::Open { event_id, .. }
| AccountEvent::Add { event_id, .. }
| AccountEvent::Remove { event_id, .. } => event_id,
}
}
}
#[derive(Error, Debug)]
pub enum DomainError {
#[error("This event can't be applied given the current state of the aggregate")]
InvalidState,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum SideEffects {
PublishMessage {
id: Uuid,
message: String,
},
SendEmail {
id: Uuid,
address: String,
content: String,
},
}
impl SideEffect for SideEffects {
type SideEffectId = Uuid;
fn id(&self) -> &Self::SideEffectId {
match self {
SideEffects::PublishMessage { id, .. } | SideEffects::SendEmail { id, .. } => id,
}
}
}
impl Aggregate for Account {
const SNAPSHOT_VERSION: u64 = 2;
type AggregateId = Uuid;
type DomainEvent = AccountEvent;
type ApplyError = DomainError;
type SideEffect = SideEffects;
fn aggregate_id(&self) -> &Self::AggregateId {
&self.account_id
}
fn apply(&mut self, event: &Self::DomainEvent) -> Result<(), Self::ApplyError> {
match event {
AccountEvent::Add { amount, .. } => {
self.balance += amount;
}
AccountEvent::Remove { amount, .. } => {
self.balance -= amount;
}
AccountEvent::Open { .. } => return Err(Self::ApplyError::InvalidState),
}
Ok(())
}
fn apply_new(event: &Self::DomainEvent) -> Result<Self, Self::ApplyError> {
match event {
AccountEvent::Open {
account_id,
starting_balance,
..
} => Ok(Self {
account_id: *account_id,
balance: *starting_balance,
}),
AccountEvent::Add { .. } | AccountEvent::Remove { .. } => {
Err(Self::ApplyError::InvalidState)
}
}
}
fn side_effects(&self, event: &Self::DomainEvent) -> Option<Vec<Self::SideEffect>> {
let side_effect = match event {
AccountEvent::Open {
account_id,
event_id,
email,
starting_balance,
} => Some(SideEffects::SendEmail {
id: *event_id,
address: email.clone(),
content: format!(
"Account opened with id {account_id} and starting balance {starting_balance}"
),
}),
AccountEvent::Add { event_id, amount } => Some(SideEffects::PublishMessage {
id: *event_id,
message: amount.to_string(),
}),
AccountEvent::Remove { .. } => None,
};
side_effect.map(|s| vec![s])
}
}