Skip to main content

event_sourcing/
event_sourcing.rs

1//! Advanced: rebuild a counter from events rather than loading its saved state.
2//! 発展編: 現在値の保存ではなく、過去の事実を適用してCounterを復元する。
3//!
4//! Run: cargo run -p ddd-toolkit --example event_sourcing --locked
5//! Read main first: old event (+2) → replay → snapshot → append (+3) → restore (5).
6//! main から読む: 旧形式の+2 → 再生 → スナップショット → +3追記 → 5に復元。
7//! Revisions start at 0. ExpectedRevision rejects a concurrent/stale write.
8//! リビジョンは0始まり。期待した位置と異なる書き込みは競合として拒否する。
9//! A snapshot is a cache; an upcaster converts legacy event data on load.
10//! スナップショットは途中状態のキャッシュ、upcasterは旧イベント形式の変換。
11//! The event store retains the full history alongside the snapshot.
12//! スナップショット保存後も、イベントストアは全履歴を保持する。
13use ddd_toolkit::{
14    AggregateReplay, AggregateRoot as _, AggregateSnapshot, AppendError, AppendResult,
15    CurrentRevision, EventStore, EventUpcaster, ExpectedRevision, LoadResult, NoStream,
16    RevisionConflict, SnapshotLoadError, SnapshotSaveError, SnapshotStore, SnapshotUse,
17    StreamHistory, StreamRevision, replay_aggregate, replay_with_optional_snapshot,
18};
19use std::{collections::HashMap, convert::Infallible};
20
21#[derive(Debug, Clone, PartialEq, ddd_toolkit::DomainEvent)]
22enum Added {
23    V1(u32),
24    V2 { amount: u32 },
25}
26struct ToV2;
27impl EventUpcaster<Added> for ToV2 {
28    type Error = Infallible;
29    fn upcast(&self, event: Added) -> Result<Added, Infallible> {
30        Ok(match event {
31            Added::V1(amount) => Added::V2 { amount },
32            current => current,
33        })
34    }
35}
36#[derive(Debug, ddd_toolkit::Entity, ddd_toolkit::AggregateRoot)]
37struct Counter {
38    #[entity(id)]
39    id: u64,
40    value: u32,
41    #[aggregate(events)]
42    events: Vec<Added>,
43}
44impl AggregateReplay for Counter {
45    type InitializationError = Infallible;
46    type ApplicationError = &'static str;
47    type Snapshot = u32;
48    type SnapshotValidationError = Infallible;
49    type SnapshotStateError = Infallible;
50    fn initial_state(id: &u64) -> Result<Self, Infallible> {
51        Ok(Self {
52            id: *id,
53            value: 0,
54            events: vec![],
55        })
56    }
57    fn apply_event(&mut self, event: &Added) -> Result<(), Self::ApplicationError> {
58        let Added::V2 { amount } = event else {
59            return Err("upcast before replay");
60        };
61        self.value = self.value.checked_add(*amount).ok_or("counter overflow")?;
62        Ok(()) // Historical events must not become pending events.
63    }
64    fn classify_snapshot(
65        _: &AggregateSnapshot<u32>,
66        _: StreamRevision,
67    ) -> Result<SnapshotUse, Infallible> {
68        // This example has only one snapshot schema.
69        Ok(SnapshotUse::Applicable)
70    }
71    fn from_snapshot(id: &u64, snapshot: AggregateSnapshot<u32>) -> Result<Self, Infallible> {
72        Ok(Self {
73            id: *id,
74            value: snapshot.into_parts().0,
75            events: vec![],
76        })
77    }
78}
79
80#[derive(Default)]
81struct MemoryEvents(HashMap<u64, Vec<Added>>);
82impl EventStore for MemoryEvents {
83    type Aggregate = Counter;
84    type Error = Infallible;
85    fn load(&self, id: &u64) -> Result<LoadResult<Added>, Infallible> {
86        match self.0.get(id) {
87            None => Ok(LoadResult::NoStream(NoStream)),
88            Some(events) => {
89                // Real adapters deserialize and select migrations at this boundary.
90                let current = events
91                    .iter()
92                    .cloned()
93                    .map(|event| ToV2.upcast(event).unwrap())
94                    .collect();
95                Ok(LoadResult::Found(
96                    StreamHistory::try_new(current, StreamRevision::new(events.len() as u64 - 1))
97                        .unwrap(),
98                ))
99            }
100        }
101    }
102    fn append(
103        &mut self,
104        id: &u64,
105        expected: ExpectedRevision,
106        events: Vec<Added>,
107    ) -> Result<AppendResult, AppendError<Infallible>> {
108        if events.is_empty() {
109            return Err(AppendError::EmptyBatch);
110        }
111        let current = self
112            .0
113            .get(id)
114            .map(|events| StreamRevision::new(events.len() as u64 - 1));
115        let matches = match (expected, current) {
116            (ExpectedRevision::NoStream(_), None) => true,
117            (ExpectedRevision::Revision(want), Some(have)) => want == have,
118            _ => false,
119        };
120        if !matches {
121            return Err(AppendError::Conflict(RevisionConflict {
122                expected,
123                current: current.map_or(
124                    CurrentRevision::NoStream(NoStream),
125                    CurrentRevision::Revision,
126                ),
127            }));
128        }
129        let revision = match current {
130            None => StreamRevision::new(events.len() as u64 - 1),
131            Some(revision) => StreamRevision::new(
132                revision
133                    .value()
134                    .checked_add(events.len() as u64)
135                    .ok_or(AppendError::RevisionExhausted { current: revision })?,
136            ),
137        };
138        // Exclusive access makes the check + append atomic only in this process.
139        self.0.entry(*id).or_default().extend(events);
140        Ok(AppendResult {
141            resulting_revision: revision,
142        })
143    }
144}
145#[derive(Default)]
146struct MemorySnapshots(HashMap<u64, AggregateSnapshot<u32>>);
147impl SnapshotStore for MemorySnapshots {
148    type Aggregate = Counter;
149    type State = u32;
150    type Error = Infallible;
151    fn load(
152        &self,
153        id: &u64,
154    ) -> Result<Option<AggregateSnapshot<u32>>, SnapshotLoadError<Infallible>> {
155        Ok(self
156            .0
157            .get(id)
158            .map(|snapshot| AggregateSnapshot::new(*snapshot.state(), snapshot.revision())))
159    }
160    fn save(
161        &mut self,
162        id: &u64,
163        snapshot: AggregateSnapshot<u32>,
164    ) -> Result<(), SnapshotSaveError<Infallible>> {
165        self.0.insert(*id, snapshot);
166        Ok(())
167    }
168}
169fn history(store: &MemoryEvents) -> StreamHistory<Added> {
170    let LoadResult::Found(history) = store.load(&1).unwrap() else {
171        panic!("stream missing");
172    };
173    history
174}
175fn main() {
176    let mut store = MemoryEvents::default();
177    assert!(matches!(store.load(&1).unwrap(), LoadResult::NoStream(_)));
178    let first = store
179        .append(&1, ExpectedRevision::NoStream(NoStream), vec![Added::V1(2)])
180        .unwrap();
181    assert_eq!(first.resulting_revision, StreamRevision::new(0));
182    let mut replayed = replay_aggregate::<Counter>(&1, &history(&store)).unwrap();
183    assert_eq!(replayed.aggregate.value, 2);
184    assert!(replayed.aggregate.pull_events().is_empty());
185    let mut snapshots = MemorySnapshots::default();
186    snapshots
187        .save(
188            &1,
189            AggregateSnapshot::new(replayed.aggregate.value, replayed.revision),
190        )
191        .unwrap();
192    store
193        .append(
194            &1,
195            ExpectedRevision::Revision(replayed.revision),
196            vec![Added::V2 { amount: 3 }],
197        )
198        .unwrap();
199    assert!(matches!(
200        store.append(
201            &1,
202            ExpectedRevision::Revision(replayed.revision),
203            vec![Added::V1(99)]
204        ),
205        Err(AppendError::Conflict(_))
206    ));
207    let history = history(&store);
208    let mut restored =
209        replay_with_optional_snapshot::<Counter>(&1, &history, snapshots.load(&1).unwrap())
210            .unwrap();
211    let full = replay_aggregate::<Counter>(&1, &history).unwrap();
212    assert_eq!(restored.aggregate.value, 5);
213    assert_eq!(restored.aggregate.value, full.aggregate.value);
214    assert_eq!(restored.revision, StreamRevision::new(1));
215    assert!(restored.aggregate.pull_events().is_empty());
216    println!("event_sourcing: upcast v1; rejected stale write; snapshot + tail = 5");
217}