ddd_toolkit_core/async_outbox_lifecycle.rs
1#![allow(clippy::type_complexity)]
2
3use crate::{AggregateRoot, AppendError, AppendResult, AsyncTransactionalOutbox, ExpectedRevision};
4use std::future::Future;
5use std::pin::Pin;
6
7/// **English:**<br>
8/// Failure of an asynchronous enqueue attempt or rejection of a repeated attempt.
9///
10/// **日本語:**<br>
11/// 非同期登録の試行による失敗、または繰り返しの試行に対する拒否です。
12#[derive(Debug, PartialEq, Eq)]
13pub enum AsyncTransactionalOutboxOperationError<E> {
14 /// **English:**<br>
15 /// The outbox's append error, including validation, revision, or storage failures.
16 /// Pulled events are not restored when this error is returned.
17 ///
18 /// **日本語:**<br>
19 /// 検証、リビジョン、ストレージの失敗を含むアウトボックスの追加エラーです。
20 /// このエラーを返す際、取り出したイベントは復元しません。
21 Adapter(AppendError<E>),
22
23 /// **English:**<br>
24 /// A previous call started an attempt, even if its future was never polled.
25 /// The rejected call does not pull events or invoke the adapter.
26 ///
27 /// **日本語:**<br>
28 /// 以前の呼び出しで試行を開始済みです。その Future が未ポーリングでも該当します。
29 /// 拒否された呼び出しでは、イベントを取り出さず、アダプターも呼び出しません。
30 AlreadyEnqueued,
31}
32
33/// **English:**<br>
34/// Reason completion could not confirm a successful asynchronous enqueue.
35///
36/// **日本語:**<br>
37/// 完了確認で非同期登録の成功を確認できなかった理由です。
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum AsyncTransactionalOutboxCompletionError {
40 /// **English:**<br>
41 /// No call to enqueue was made before completing the context.
42 ///
43 /// **日本語:**<br>
44 /// コンテキストの完了確認までに登録が呼び出されていません。
45 MissingEnqueue,
46
47 /// **English:**<br>
48 /// An attempt started without recorded success, including an error or dropped future.
49 /// The state is set before pulling events, so a caught panic can also leave this result.
50 ///
51 /// **日本語:**<br>
52 /// エラーや Future の破棄などにより、試行開始後に成功が記録されていません。
53 /// イベントの取り出し前にこの状態を設定するため、パニックを捕捉した後もこの結果になり得ます。
54 EnqueueFailedConsumed,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58enum OperationState {
59 Pending,
60 Succeeded,
61 FailedConsumed,
62}
63
64/// **English:**<br>
65/// Single-attempt asynchronous enqueue tracking with exclusive aggregate and outbox borrows.
66///
67/// Calling [`Self::save_and_enqueue`] consumes the attempt and pulls events immediately, before
68/// its future is polled. Failure or dropping that future does not restore events or enable retry.
69/// Only a successful adapter result records success. [`Self::complete`] checks this record;
70/// it does not commit a transaction. Dropping the context performs no check or rollback.
71/// Transaction and cancellation effects on storage belong to the outbox adapter.
72///
73/// The aggregate must be `Send + Sync`, its event must be `Send`, and the outbox and error must
74/// be `Send` to support the returned `Send` future. No executor or locking is provided.
75///
76/// Await a successful enqueue before consuming the context to check completion.
77/// The assertion follows from recording success after the adapter returns `Ok`.
78///
79/// **日本語:**<br>
80/// 集約とアウトボックスを排他的に借用し、非同期登録の一回の試行を追跡します。
81///
82/// [`Self::save_and_enqueue`] の呼び出し時に、Future のポーリング前に試行を消費して
83/// イベントを取り出します。失敗や Future の破棄でもイベントは復元せず、再試行もできません。
84/// アダプターの成功結果だけが成功を記録します。[`Self::complete`] はこの記録を検査するだけで、
85/// コミットしません。コンテキストの破棄でも検査やロールバックは行いません。
86/// トランザクションとキャンセルがストレージに及ぼす効果はアダプターが定めます。
87///
88/// `Send` な Future のため、集約には `Send + Sync`、イベント、アウトボックス、エラーには
89/// `Send` が必要です。実行器やロックは提供しません。
90///
91/// 登録成功を待ってからコンテキストを消費して完了確認します。
92/// アダプターが `Ok` を返した後に成功を記録するため、以下のアサーションが成立します。
93///
94/// # Example / 使用例
95///
96/// ```rust
97/// use ddd_toolkit_core::{
98/// AggregateRoot, AppendResult, AsyncTransactionalOutbox,
99/// AsyncTransactionalOutboxLifecycleContext, AsyncTransactionalOutboxOperationError,
100/// ExpectedRevision,
101/// };
102///
103/// async fn enqueue<A, O>(
104/// aggregate: &mut A,
105/// outbox: &mut O,
106/// expected: ExpectedRevision,
107/// ) -> Result<AppendResult, AsyncTransactionalOutboxOperationError<O::Error>>
108/// where
109/// A: AggregateRoot + Send + Sync,
110/// A::Event: Send,
111/// O: AsyncTransactionalOutbox<Aggregate = A> + Send,
112/// {
113/// let mut context = AsyncTransactionalOutboxLifecycleContext::new(aggregate, outbox, expected);
114/// let result = context.save_and_enqueue().await?;
115/// assert_eq!(context.complete(), Ok(()));
116/// Ok(result)
117/// }
118/// ```
119pub struct AsyncTransactionalOutboxLifecycleContext<'storage, A, O>
120where
121 A: AggregateRoot + Send + Sync,
122 A::Event: Send,
123 O: AsyncTransactionalOutbox<Aggregate = A> + Send,
124 O::Error: Send,
125{
126 aggregate: &'storage mut A,
127 outbox: &'storage mut O,
128 expected: ExpectedRevision,
129 state: OperationState,
130}
131
132impl<'storage, A, O> AsyncTransactionalOutboxLifecycleContext<'storage, A, O>
133where
134 A: AggregateRoot + Send + Sync,
135 A::Event: Send,
136 O: AsyncTransactionalOutbox<Aggregate = A> + Send,
137 O::Error: Send,
138{
139 /// **English:**<br>
140 /// Borrows the aggregate and outbox for `'storage` and stores `expected` unchanged.
141 /// The context starts pending; construction does not pull events or call the adapter.
142 ///
143 /// **日本語:**<br>
144 /// 集約とアウトボックスを `'storage` の間借用し、`expected` を変更せず保持します。
145 /// 未試行で開始し、構築時にはイベントを取り出さず、アダプターも呼び出しません。
146 pub fn new(
147 aggregate: &'storage mut A,
148 outbox: &'storage mut O,
149 expected: ExpectedRevision,
150 ) -> Self {
151 Self {
152 aggregate,
153 outbox,
154 expected,
155 state: OperationState::Pending,
156 }
157 }
158
159 /// **English:**<br>
160 /// Immediately reserves the attempt and pulls events, then returns a boxed `Send` future.
161 ///
162 /// The future borrows this context for `'ctx`. Polling it calls the adapter with the borrowed
163 /// aggregate, stored revision, and owned batch, including an empty batch. It returns the
164 /// [`AppendResult`] or wraps the unchanged [`AppendError`]. Dropping the future before its first
165 /// poll discards the pulled batch without calling the adapter; dropping it while pending leaves
166 /// the attempt unsuccessful, with storage effects adapter-defined. Neither path restores events.
167 /// A later call returns a future yielding [`AsyncTransactionalOutboxOperationError::AlreadyEnqueued`].
168 ///
169 /// Panics from pulling events or invoking/polling the adapter propagate. State is marked
170 /// unsuccessful before pulling; catching a panic does not enable another attempt.
171 ///
172 /// **日本語:**<br>
173 /// 呼び出し時に試行を確保してイベントを取り出し、ボックス化された `Send` Future を返します。
174 ///
175 /// Future はコンテキストを `'ctx` の間借用します。ポーリング時に、借用した集約、保存済み
176 /// リビジョン、所有されたバッチをアダプターへ渡します。空バッチも渡します。
177 /// [`AppendResult`] を返すか、変更していない [`AppendError`] を包んで返します。
178 /// 初回ポーリング前の破棄ではアダプターを呼ばずにバッチを破棄します。待機中の破棄では
179 /// 試行が未成功のままとなり、ストレージへの効果はアダプターに依存します。
180 /// どちらもイベントを復元しません。後続の呼び出しは
181 /// [`AsyncTransactionalOutboxOperationError::AlreadyEnqueued`] を返す Future を返します。
182 ///
183 /// イベントの取り出しやアダプターの呼び出し・ポーリングのパニックは伝播します。
184 /// 取り出し前に未成功と記録するため、パニックを捕捉しても再試行できません。
185 pub fn save_and_enqueue<'ctx>(
186 &'ctx mut self,
187 ) -> Pin<
188 Box<
189 dyn Future<
190 Output = Result<AppendResult, AsyncTransactionalOutboxOperationError<O::Error>>,
191 > + Send
192 + 'ctx,
193 >,
194 >
195 where
196 'storage: 'ctx,
197 A: 'ctx,
198 A::Event: 'ctx,
199 O: 'ctx,
200 O::Error: 'ctx,
201 {
202 if self.state != OperationState::Pending {
203 return Box::pin(async {
204 Err(AsyncTransactionalOutboxOperationError::AlreadyEnqueued)
205 });
206 }
207
208 self.state = OperationState::FailedConsumed;
209 let state = &mut self.state;
210 let events = self.aggregate.pull_events();
211 let aggregate: &'ctx A = &*self.aggregate;
212 let outbox: &'ctx mut O = &mut *self.outbox;
213 let expected = self.expected;
214
215 Box::pin(async move {
216 let mut guard = AttemptGuard::new(state);
217 match outbox.save_and_enqueue(aggregate, expected, events).await {
218 Ok(result) => {
219 guard.succeed();
220 Ok(result)
221 }
222 Err(error) => Err(AsyncTransactionalOutboxOperationError::Adapter(error)),
223 }
224 })
225 }
226
227 /// **English:**<br>
228 /// Consumes the context and returns `Ok(())` only after recorded adapter success.
229 /// No enqueue call yields [`AsyncTransactionalOutboxCompletionError::MissingEnqueue`]; a call
230 /// without recorded success yields [`AsyncTransactionalOutboxCompletionError::EnqueueFailedConsumed`].
231 /// This makes no storage call. Rejection of a repeated attempt does not erase an earlier success.
232 /// Any returned enqueue future must release its mutable borrow before this method is called.
233 ///
234 /// **日本語:**<br>
235 /// コンテキストを消費し、アダプターの成功を記録済みの場合のみ `Ok(())` を返します。
236 /// 登録未呼び出しなら [`AsyncTransactionalOutboxCompletionError::MissingEnqueue`]、呼び出し済みで
237 /// 未成功なら [`AsyncTransactionalOutboxCompletionError::EnqueueFailedConsumed`] を返します。
238 /// ストレージは呼び出しません。再試行の拒否は以前の成功を消しません。
239 /// 呼び出す前に、登録の Future が保持する可変借用を解放する必要があります。
240 pub fn complete(self) -> Result<(), AsyncTransactionalOutboxCompletionError> {
241 match self.state {
242 OperationState::Succeeded => Ok(()),
243 OperationState::Pending => Err(AsyncTransactionalOutboxCompletionError::MissingEnqueue),
244 OperationState::FailedConsumed => {
245 Err(AsyncTransactionalOutboxCompletionError::EnqueueFailedConsumed)
246 }
247 }
248 }
249}
250
251struct AttemptGuard<'a> {
252 state: &'a mut OperationState,
253 succeeded: bool,
254}
255
256impl<'a> AttemptGuard<'a> {
257 fn new(state: &'a mut OperationState) -> Self {
258 Self {
259 state,
260 succeeded: false,
261 }
262 }
263
264 fn succeed(&mut self) {
265 *self.state = OperationState::Succeeded;
266 self.succeeded = true;
267 }
268}
269
270impl Drop for AttemptGuard<'_> {
271 fn drop(&mut self) {
272 if !self.succeeded {
273 *self.state = OperationState::FailedConsumed;
274 }
275 }
276}
277
278/// **English:**<br>
279/// Short alias for [`AsyncTransactionalOutboxLifecycleContext`] with the same cancellation rules.
280///
281/// **日本語:**<br>
282/// キャンセルの規則も同じ [`AsyncTransactionalOutboxLifecycleContext`] の短い別名です。
283pub type AsyncOutboxLifecycleContext<'a, A, O> = AsyncTransactionalOutboxLifecycleContext<'a, A, O>;
284
285/// **English:**<br>
286/// Alias for [`AsyncTransactionalOutboxOperationError`] covering enqueue and repeated-attempt errors.
287///
288/// **日本語:**<br>
289/// 登録と再試行拒否のエラーを表す [`AsyncTransactionalOutboxOperationError`] の別名です。
290pub type AsyncOutboxLifecycleOperationError<E> = AsyncTransactionalOutboxOperationError<E>;
291
292/// **English:**<br>
293/// Alias for [`AsyncTransactionalOutboxCompletionError`] used for the final state check.
294///
295/// **日本語:**<br>
296/// 最終状態の確認に使う [`AsyncTransactionalOutboxCompletionError`] の別名です。
297pub type AsyncOutboxLifecycleCompletionError = AsyncTransactionalOutboxCompletionError;