Skip to main content

ddd_toolkit_core/
replay.rs

1use crate::AggregateRoot;
2use crate::event_store::{StreamHistory, StreamRevision};
3use crate::snapshot::{AggregateSnapshot, SnapshotUse};
4
5/// **English:**<br>
6/// Aggregate hooks for reconstructing state from stored events or a snapshot.
7/// Implementations should apply historical events without recording new pending events.
8/// The replay helpers do not clear queues, check aggregate identity, publish events, or catch
9/// panics from these hooks. Error and panic conditions are defined by each implementation.
10///
11/// **日本語:**<br>
12/// 保存イベントまたはスナップショットから状態を復元するための集約フックです。
13/// 実装では履歴イベントの適用で新たな保留イベントを記録しないようにします。
14/// 再生ヘルパーはキューの消去、集約識別子の確認、イベント発行、フックのパニック捕捉をしません。
15/// エラーとパニックの条件は各実装が定めます。
16pub trait AggregateReplay: AggregateRoot {
17    /// **English:**<br>
18    /// Failure to create the pre-event state.
19    ///
20    /// **日本語:**<br>
21    /// イベント適用前の状態の作成失敗です。
22    type InitializationError;
23    /// **English:**<br>
24    /// Failure to apply a historical event.
25    ///
26    /// **日本語:**<br>
27    /// 履歴イベントの適用失敗です。
28    type ApplicationError;
29    /// **English:**<br>
30    /// State representation consumed when restoring a snapshot.
31    ///
32    /// **日本語:**<br>
33    /// スナップショット復元時に消費する状態表現です。
34    type Snapshot;
35    /// **English:**<br>
36    /// Failure to classify a snapshot, distinct from choosing full replay.
37    ///
38    /// **日本語:**<br>
39    /// スナップショット分類の失敗です。全件再生を選ぶこととは区別されます。
40    type SnapshotValidationError;
41    /// **English:**<br>
42    /// Failure to construct aggregate state from an accepted snapshot.
43    ///
44    /// **日本語:**<br>
45    /// 採用したスナップショットからの集約状態構築失敗です。
46    type SnapshotStateError;
47
48    /// **English:**<br>
49    /// Creates state for `id` before any historical events are applied.
50    /// Returns an implementation-defined error if initialization fails.
51    ///
52    /// **日本語:**<br>
53    /// 履歴イベント適用前の `id` の状態を作成します。
54    /// 初期化に失敗した場合は実装定義のエラーを返します。
55    fn initial_state(id: &Self::Id) -> Result<Self, Self::InitializationError>
56    where
57        Self: Sized;
58
59    /// **English:**<br>
60    /// Updates state using one borrowed historical event, without emitting it again.
61    /// Returns an implementation-defined error on failure; mutations before that error are not undone.
62    ///
63    /// **日本語:**<br>
64    /// 借用した履歴イベント 1 件で状態を更新し、そのイベントを再発行しません。
65    /// 失敗時は実装定義のエラーを返します。それ以前の状態変更は元に戻されません。
66    fn apply_event(&mut self, event: &Self::Event) -> Result<(), Self::ApplicationError>;
67
68    /// **English:**<br>
69    /// Classifies `snapshot` against the complete history's `stream_revision`.
70    /// Return [`SnapshotUse::Applicable`] to restore it, or another classification to replay all events.
71    /// An error aborts replay rather than falling back. Compatibility and staleness policy belong here.
72    ///
73    /// **日本語:**<br>
74    /// 完全な履歴の `stream_revision` に照らして `snapshot` を分類します。
75    /// 復元するなら [`SnapshotUse::Applicable`]、全件再生するなら他の分類を返します。
76    /// エラー時はフォールバックせず再生を中断します。互換性と古さの方針をここで定めます。
77    fn classify_snapshot(
78        snapshot: &AggregateSnapshot<Self::Snapshot>,
79        stream_revision: StreamRevision,
80    ) -> Result<SnapshotUse, Self::SnapshotValidationError>;
81
82    /// **English:**<br>
83    /// Consumes an accepted snapshot to construct the aggregate for `id`.
84    /// Returns an implementation-defined state error; replay does not retry from the initial state.
85    ///
86    /// **日本語:**<br>
87    /// 採用済みスナップショットを消費し、`id` の集約を構築します。
88    /// 失敗時は実装定義の状態エラーを返し、初期状態からの再試行は行いません。
89    fn from_snapshot(
90        id: &Self::Id,
91        snapshot: AggregateSnapshot<Self::Snapshot>,
92    ) -> Result<Self, Self::SnapshotStateError>
93    where
94        Self: Sized;
95}
96
97/// **English:**<br>
98/// Reconstructed aggregate paired with the history's final revision.
99/// This revision can be used as [`crate::ExpectedRevision::Revision`] for a subsequent append.
100///
101/// **日本語:**<br>
102/// 復元した集約と履歴の最終リビジョンです。
103/// このリビジョンは次の追加の [`crate::ExpectedRevision::Revision`] として利用できます。
104#[derive(Debug, Eq, PartialEq)]
105pub struct RehydratedAggregate<A> {
106    /// **English:**<br>
107    /// Aggregate after all selected historical events were applied.
108    ///
109    /// **日本語:**<br>
110    /// 選択された履歴イベントをすべて適用した後の集約です。
111    pub aggregate: A,
112    /// **English:**<br>
113    /// Final revision of the complete input history, including when no tail events remain.
114    ///
115    /// **日本語:**<br>
116    /// 後続イベントが残っていない場合も含め、入力された完全な履歴の最終リビジョンです。
117    pub revision: StreamRevision,
118}
119
120/// **English:**<br>
121/// First failure encountered during full-history reconstruction.
122///
123/// **日本語:**<br>
124/// 全履歴からの復元で最初に発生した失敗です。
125#[derive(Debug, Eq, PartialEq)]
126pub enum ReplayError<I, P> {
127    /// **English:**<br>
128    /// Creating the initial aggregate state failed.
129    ///
130    /// **日本語:**<br>
131    /// 集約の初期状態の作成に失敗しました。
132    Initialization(I),
133    /// **English:**<br>
134    /// Applying a historical event failed.
135    ///
136    /// **日本語:**<br>
137    /// 履歴イベントの適用に失敗しました。
138    Application(P),
139}
140
141/// **English:**<br>
142/// First failure during snapshot-assisted reconstruction; errors do not trigger fallback.
143///
144/// **日本語:**<br>
145/// スナップショットを用いた復元で最初に発生した失敗です。エラー時はフォールバックしません。
146#[derive(Debug, Eq, PartialEq)]
147pub enum SnapshotReplayError<I, P, V, S> {
148    /// **English:**<br>
149    /// Creating initial state for full replay failed.
150    ///
151    /// **日本語:**<br>
152    /// 全件再生のための初期状態の作成に失敗しました。
153    Initialization(I),
154    /// **English:**<br>
155    /// The snapshot classification hook returned an error.
156    ///
157    /// **日本語:**<br>
158    /// スナップショット分類フックがエラーを返しました。
159    SnapshotValidation(V),
160    /// **English:**<br>
161    /// Restoring the accepted snapshot failed.
162    ///
163    /// **日本語:**<br>
164    /// 採用したスナップショットの復元に失敗しました。
165    SnapshotState(S),
166    /// **English:**<br>
167    /// An applicable snapshot's tail could not be selected by [`StreamHistory::events_after`].
168    /// This includes a snapshot ahead of history or an unrepresentable next index.
169    ///
170    /// **日本語:**<br>
171    /// 採用可能なスナップショットの後続部分を [`StreamHistory::events_after`] で選べませんでした。
172    /// 履歴より先のスナップショットや、表現できない次の添字が該当します。
173    SnapshotRevision {
174        /// **English:**<br>
175        /// Revision carried by the accepted snapshot.
176        ///
177        /// **日本語:**<br>
178        /// 採用したスナップショットが保持するリビジョンです。
179        snapshot_revision: StreamRevision,
180        /// **English:**<br>
181        /// Final revision of the supplied history.
182        ///
183        /// **日本語:**<br>
184        /// 指定された履歴の最終リビジョンです。
185        history_revision: StreamRevision,
186    },
187    /// **English:**<br>
188    /// Applying an event from the selected history failed.
189    ///
190    /// **日本語:**<br>
191    /// 選択された履歴のイベント適用に失敗しました。
192    Application(P),
193}
194
195/// **English:**<br>
196/// Initializes `A` for `id` and applies every event in `history` in stored order.
197/// Returns the reconstructed aggregate and history revision. The first hook error is wrapped in
198/// [`ReplayError`]; the local aggregate is dropped on error. No storage or publishing is performed,
199/// and hook panics propagate. The caller must supply history belonging to `id`.
200///
201/// This counter restores two historical increments without queuing new events.
202///
203/// **日本語:**<br>
204/// `id` の `A` を初期化し、`history` の全イベントを保存順に適用します。
205/// 復元した集約と履歴リビジョンを返します。最初のフックエラーを [`ReplayError`] で包み、
206/// エラー時はローカルな集約を破棄します。保存や発行は行わず、フックのパニックは伝播します。
207/// 呼び出し側は `id` に属する履歴を指定する必要があります。
208///
209/// このカウンターは新規イベントを保留せず、履歴の加算 2 件を復元します。
210///
211/// # Example / 使用例
212///
213/// ```rust
214/// use std::convert::Infallible;
215/// use ddd_toolkit_core::{
216///     AggregateReplay, AggregateRoot, AggregateSnapshot, DomainEvent, Entity,
217///     SnapshotUse, StreamHistory, StreamRevision, replay_aggregate,
218/// };
219/// struct Increment(u64);
220/// impl DomainEvent for Increment {}
221/// struct Counter { id: u64, total: u64, pending: Vec<Increment> }
222/// impl Entity for Counter {
223///     type Id = u64;
224///     fn id(&self) -> &u64 { &self.id }
225/// }
226/// impl AggregateRoot for Counter {
227///     type Event = Increment;
228///     fn record_event(&mut self, event: Increment) { self.pending.push(event); }
229///     fn pull_events(&mut self) -> Vec<Increment> { std::mem::take(&mut self.pending) }
230/// }
231/// impl AggregateReplay for Counter {
232///     type InitializationError = Infallible;
233///     type ApplicationError = &'static str;
234///     type Snapshot = u64;
235///     type SnapshotValidationError = Infallible;
236///     type SnapshotStateError = Infallible;
237///     fn initial_state(id: &u64) -> Result<Self, Infallible> {
238///         Ok(Self { id: *id, total: 0, pending: vec![] })
239///     }
240///     fn apply_event(&mut self, event: &Increment) -> Result<(), &'static str> {
241///         self.total = self.total.checked_add(event.0).ok_or("counter overflow")?;
242///         Ok(())
243///     }
244///     fn classify_snapshot(_: &AggregateSnapshot<u64>, _: StreamRevision)
245///         -> Result<SnapshotUse, Infallible> { Ok(SnapshotUse::Applicable) }
246///     fn from_snapshot(id: &u64, snapshot: AggregateSnapshot<u64>) -> Result<Self, Infallible> {
247///         Ok(Self { id: *id, total: snapshot.into_parts().0, pending: vec![] })
248///     }
249/// }
250/// let history = StreamHistory::try_new(vec![Increment(2), Increment(3)], StreamRevision::new(1))
251///     .unwrap();
252/// let mut restored = replay_aggregate::<Counter>(&7, &history).unwrap();
253/// assert_eq!(restored.aggregate.total, 5);
254/// assert_eq!(restored.revision, StreamRevision::new(1));
255/// assert!(restored.aggregate.pull_events().is_empty());
256/// ```
257pub fn replay_aggregate<A>(
258    id: &A::Id,
259    history: &StreamHistory<A::Event>,
260) -> Result<RehydratedAggregate<A>, ReplayError<A::InitializationError, A::ApplicationError>>
261where
262    A: AggregateReplay,
263{
264    let mut aggregate = A::initial_state(id).map_err(ReplayError::Initialization)?;
265    for event in history.events() {
266        aggregate
267            .apply_event(event)
268            .map_err(ReplayError::Application)?;
269    }
270
271    Ok(RehydratedAggregate {
272        aggregate,
273        revision: history.revision(),
274    })
275}
276
277/// **English:**<br>
278/// Reconstructs `A` using an optional snapshot and a complete `history` for `id`.
279/// With no snapshot, or a [`SnapshotUse::Stale`] or [`SnapshotUse::Inapplicable`] classification,
280/// initializes state and replays all events. For [`SnapshotUse::Applicable`], selects events strictly
281/// after the snapshot revision, restores state, then applies that tail in order. A snapshot at the
282/// history's final revision requires no event applications.
283///
284/// Classification errors abort; an invalid tail yields [`SnapshotReplayError::SnapshotRevision`]
285/// before state restoration. Initialization, restoration, and application errors are wrapped in
286/// their corresponding variants with no fallback or retry. On failure the local state is dropped.
287/// Returns the complete history's revision on success. No storage, publishing, identity checking,
288/// or panic catching is performed; the caller must supply matching history and snapshot data.
289///
290/// **日本語:**<br>
291/// `id` の完全な `history` と任意のスナップショットから `A` を復元します。
292/// スナップショットがない場合や、分類が [`SnapshotUse::Stale`] または
293/// [`SnapshotUse::Inapplicable`] の場合は初期状態から全件再生します。
294/// [`SnapshotUse::Applicable`] ならスナップショットのリビジョンより後のイベントを選び、
295/// 状態を復元して後続イベントを順に適用します。履歴の最終リビジョンと一致すれば適用は不要です。
296///
297/// 分類エラーは処理を中断し、後続部分を選べなければ状態復元前に
298/// [`SnapshotReplayError::SnapshotRevision`] を返します。初期化・復元・適用のエラーは
299/// 対応するバリアントで包み、フォールバックや再試行はしません。失敗時はローカル状態を破棄します。
300/// 成功時は完全な履歴のリビジョンを返します。保存、発行、識別子検証、パニック捕捉は行いません。
301/// 呼び出し側は対応する履歴とスナップショットを指定する必要があります。
302#[allow(clippy::type_complexity)]
303pub fn replay_with_optional_snapshot<A>(
304    id: &A::Id,
305    history: &StreamHistory<A::Event>,
306    snapshot: Option<AggregateSnapshot<A::Snapshot>>,
307) -> Result<
308    RehydratedAggregate<A>,
309    SnapshotReplayError<
310        A::InitializationError,
311        A::ApplicationError,
312        A::SnapshotValidationError,
313        A::SnapshotStateError,
314    >,
315>
316where
317    A: AggregateReplay,
318{
319    let (mut aggregate, events) = match snapshot {
320        None => (
321            A::initial_state(id).map_err(SnapshotReplayError::Initialization)?,
322            history.events(),
323        ),
324        Some(snapshot) => {
325            let snapshot_use = A::classify_snapshot(&snapshot, history.revision())
326                .map_err(SnapshotReplayError::SnapshotValidation)?;
327            match snapshot_use {
328                SnapshotUse::Applicable => {
329                    let revision = snapshot.revision();
330                    let events = match history.events_after(revision) {
331                        Some(events) => events,
332                        None => {
333                            return Err(SnapshotReplayError::SnapshotRevision {
334                                snapshot_revision: revision,
335                                history_revision: history.revision(),
336                            });
337                        }
338                    };
339                    (
340                        A::from_snapshot(id, snapshot)
341                            .map_err(SnapshotReplayError::SnapshotState)?,
342                        events,
343                    )
344                }
345                SnapshotUse::Stale | SnapshotUse::Inapplicable => (
346                    A::initial_state(id).map_err(SnapshotReplayError::Initialization)?,
347                    history.events(),
348                ),
349            }
350        }
351    };
352
353    for event in events {
354        aggregate
355            .apply_event(event)
356            .map_err(SnapshotReplayError::Application)?;
357    }
358
359    Ok(RehydratedAggregate {
360        aggregate,
361        revision: history.revision(),
362    })
363}