Skip to main content

getting_started/
getting_started.rs

1//! Start here: build a draft order, confirm it, save it, then notify a subscriber.
2//! 最初に読む例: 下書きの注文に商品を追加し、確定・保存してから通知する。
3//! Read alongside README.md / README.ja.md in this directory.
4use ddd_toolkit::{AggregateRoot as _, DomainEventPublisher, Repository, dispatch_events};
5use std::{collections::HashMap, convert::Infallible};
6
7// 1. DOMAIN / 業務モデル
8// A fact in the past tense, not a request to perform an action.
9// 過去に起きた事実。「確定せよ」という命令ではなく「確定された」という通知。
10#[derive(Debug, Clone, PartialEq, ddd_toolkit::DomainEvent)]
11struct OrderConfirmed {
12    order_id: u64,
13    total_yen: u32,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17enum OrderError {
18    ZeroPrice,
19    TotalOverflow,
20    EmptyOrder,
21    AlreadyConfirmed,
22}
23
24// Aggregate methods enforce business rules; derives implement toolkit traits.
25// Private fields restrict direct access to this module; main also uses the methods.
26// 集約のメソッドで業務ルールを実装し、deriveでtoolkitのtraitを実装する。
27// 非公開フィールドに直接アクセスできるのは同じモジュール内。mainも変更はメソッド経由。
28#[derive(Clone, ddd_toolkit::Entity, ddd_toolkit::AggregateRoot)]
29struct Order {
30    #[entity(id)]
31    id: u64,
32    total_yen: u32,
33    confirmed: bool,
34    #[aggregate(events)]
35    events: Vec<OrderConfirmed>,
36}
37impl Order {
38    fn draft(id: u64) -> Self {
39        Self {
40            id,
41            total_yen: 0,
42            confirmed: false,
43            events: vec![],
44        }
45    }
46
47    // In this deliberately small shop, add one item at a time in whole yen.
48    // 教材用に、商品は1個ずつ・価格は整数の円で扱う(税・配送・決済は対象外)。
49    fn add_item(&mut self, price_yen: u32) -> Result<(), OrderError> {
50        if self.confirmed {
51            return Err(OrderError::AlreadyConfirmed);
52        }
53        if price_yen == 0 {
54            return Err(OrderError::ZeroPrice);
55        }
56        self.total_yen = self
57            .total_yen
58            .checked_add(price_yen)
59            .ok_or(OrderError::TotalOverflow)?;
60        Ok(())
61    }
62
63    fn confirm(&mut self) -> Result<(), OrderError> {
64        if self.confirmed {
65            return Err(OrderError::AlreadyConfirmed);
66        }
67        if self.total_yen == 0 {
68            return Err(OrderError::EmptyOrder);
69        }
70        self.confirmed = true;
71        // Recording is local; it does NOT send anything to a broker.
72        // 記録は保留キューへの追加だけ。この時点では外部に通知しない。
73        self.record_event(OrderConfirmed {
74            order_id: self.id,
75            total_yen: self.total_yen,
76        });
77        Ok(())
78    }
79}
80
81// 2. APPLICATION / アプリケーションの手順
82// Read this before the adapter implementations below.
83// 下のアダプター実装より先に、この関数を読む。
84fn main() {
85    let mut order = Order::draft(1);
86    assert_eq!(order.confirm(), Err(OrderError::EmptyOrder));
87    assert_eq!(order.add_item(0), Err(OrderError::ZeroPrice));
88    order.add_item(1200).unwrap();
89    order.add_item(3000).unwrap();
90    order.confirm().unwrap();
91    assert_eq!(order.add_item(100), Err(OrderError::AlreadyConfirmed));
92    assert_eq!(order.confirm(), Err(OrderError::AlreadyConfirmed));
93    assert_eq!(order.total_yen, 4200);
94    assert_eq!(order.events.len(), 1); // Rejected operations did not emit more events.
95
96    // Persistence and notification are separate, explicitly ordered operations.
97    // 保存と通知は別操作。順番はアプリケーションが明示する。
98    let mut orders = MemoryOrders::default();
99    orders.save(&order).unwrap();
100    let saved = orders.find_by_id(&1).unwrap().unwrap();
101    assert!(saved.confirmed);
102    assert_eq!(saved.total_yen, 4200);
103    assert!(saved.events.is_empty()); // Persisted order state excludes pending notifications.
104    assert_eq!(order.events.len(), 1); // Saving did not consume the original queue.
105
106    let mut notifications = Notifications::default();
107    dispatch_events(&mut order, &mut notifications).unwrap();
108    assert_eq!(
109        notifications.sent,
110        vec![OrderConfirmed {
111            order_id: 1,
112            total_yen: 4200
113        }]
114    );
115    assert!(order.events.is_empty());
116    println!("getting_started: order #1 confirmed; saved total=4200 yen; notifications=1");
117}
118
119// 3. ADAPTERS / 外部との接続部分(読み飛ばしてから戻ってよい)
120// A repository speaks in aggregates, not SQL rows. This adapter stores a clone,
121// without pending notifications. It is not durable and has no concurrency control.
122// Repository は集約単位で保存・取得する契約。ここでは複製をメモリに保存するだけ。
123// 保留通知は保存しない。永続性・同時更新制御はない。
124#[derive(Default)]
125struct MemoryOrders(HashMap<u64, Order>);
126impl Repository for MemoryOrders {
127    type Aggregate = Order;
128    type Error = Infallible;
129    fn find_by_id(&self, id: &u64) -> Result<Option<Order>, Infallible> {
130        Ok(self.0.get(id).cloned())
131    }
132    fn save(&mut self, order: &Order) -> Result<(), Infallible> {
133        let mut saved = order.clone();
134        saved.pull_events();
135        self.0.insert(order.id, saved);
136        Ok(())
137    }
138    fn delete(&mut self, id: &u64) -> Result<(), Infallible> {
139        self.0.remove(id);
140        Ok(())
141    }
142    fn exists(&self, id: &u64) -> Result<bool, Infallible> {
143        Ok(self.0.contains_key(id))
144    }
145}
146
147// A stand-in for a subscriber that would arrange a confirmation email.
148// 確認メールを手配する購読者の代役。実際のメール送信は行わない。
149#[derive(Default)]
150struct Notifications {
151    sent: Vec<OrderConfirmed>,
152}
153impl DomainEventPublisher for Notifications {
154    type Event = OrderConfirmed;
155    type Error = Infallible;
156    fn publish(&mut self, event: &OrderConfirmed) -> Result<(), Infallible> {
157        self.sent.push(event.clone());
158        Ok(())
159    }
160}