use ddd_toolkit::{AggregateRoot as _, DomainEventPublisher, Repository, dispatch_events};
use std::{collections::HashMap, convert::Infallible};
#[derive(Debug, Clone, PartialEq, ddd_toolkit::DomainEvent)]
struct OrderConfirmed {
order_id: u64,
total_yen: u32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum OrderError {
ZeroPrice,
TotalOverflow,
EmptyOrder,
AlreadyConfirmed,
}
#[derive(Clone, ddd_toolkit::Entity, ddd_toolkit::AggregateRoot)]
struct Order {
#[entity(id)]
id: u64,
total_yen: u32,
confirmed: bool,
#[aggregate(events)]
events: Vec<OrderConfirmed>,
}
impl Order {
fn draft(id: u64) -> Self {
Self {
id,
total_yen: 0,
confirmed: false,
events: vec![],
}
}
fn add_item(&mut self, price_yen: u32) -> Result<(), OrderError> {
if self.confirmed {
return Err(OrderError::AlreadyConfirmed);
}
if price_yen == 0 {
return Err(OrderError::ZeroPrice);
}
self.total_yen = self
.total_yen
.checked_add(price_yen)
.ok_or(OrderError::TotalOverflow)?;
Ok(())
}
fn confirm(&mut self) -> Result<(), OrderError> {
if self.confirmed {
return Err(OrderError::AlreadyConfirmed);
}
if self.total_yen == 0 {
return Err(OrderError::EmptyOrder);
}
self.confirmed = true;
self.record_event(OrderConfirmed {
order_id: self.id,
total_yen: self.total_yen,
});
Ok(())
}
}
fn main() {
let mut order = Order::draft(1);
assert_eq!(order.confirm(), Err(OrderError::EmptyOrder));
assert_eq!(order.add_item(0), Err(OrderError::ZeroPrice));
order.add_item(1200).unwrap();
order.add_item(3000).unwrap();
order.confirm().unwrap();
assert_eq!(order.add_item(100), Err(OrderError::AlreadyConfirmed));
assert_eq!(order.confirm(), Err(OrderError::AlreadyConfirmed));
assert_eq!(order.total_yen, 4200);
assert_eq!(order.events.len(), 1);
let mut orders = MemoryOrders::default();
orders.save(&order).unwrap();
let saved = orders.find_by_id(&1).unwrap().unwrap();
assert!(saved.confirmed);
assert_eq!(saved.total_yen, 4200);
assert!(saved.events.is_empty()); assert_eq!(order.events.len(), 1);
let mut notifications = Notifications::default();
dispatch_events(&mut order, &mut notifications).unwrap();
assert_eq!(
notifications.sent,
vec![OrderConfirmed {
order_id: 1,
total_yen: 4200
}]
);
assert!(order.events.is_empty());
println!("getting_started: order #1 confirmed; saved total=4200 yen; notifications=1");
}
#[derive(Default)]
struct MemoryOrders(HashMap<u64, Order>);
impl Repository for MemoryOrders {
type Aggregate = Order;
type Error = Infallible;
fn find_by_id(&self, id: &u64) -> Result<Option<Order>, Infallible> {
Ok(self.0.get(id).cloned())
}
fn save(&mut self, order: &Order) -> Result<(), Infallible> {
let mut saved = order.clone();
saved.pull_events();
self.0.insert(order.id, saved);
Ok(())
}
fn delete(&mut self, id: &u64) -> Result<(), Infallible> {
self.0.remove(id);
Ok(())
}
fn exists(&self, id: &u64) -> Result<bool, Infallible> {
Ok(self.0.contains_key(id))
}
}
#[derive(Default)]
struct Notifications {
sent: Vec<OrderConfirmed>,
}
impl DomainEventPublisher for Notifications {
type Event = OrderConfirmed;
type Error = Infallible;
fn publish(&mut self, event: &OrderConfirmed) -> Result<(), Infallible> {
self.sent.push(event.clone());
Ok(())
}
}