1use std::future::Future;
2use std::marker::PhantomData;
3use std::pin::Pin;
4
5use crate::entity::Entity;
6use crate::outbox::OutboxPublisherConfig;
7use crate::queued_repo::{GetAllWithOpts, GetWithOpts, ReadOpts, UnlockableRepository};
8use crate::repository::{
9 CommitBatch, GetStream, RepositoryError, SnapshotWrite, StreamIdentity, StreamWrite,
10 TransactionalCommit,
11};
12use crate::snapshot::SnapshotRecord;
13
14use super::{hydrate, Aggregate};
15
16fn stream_identity_for<A: Aggregate>(
17 aggregate_id: &str,
18) -> Result<StreamIdentity, RepositoryError> {
19 StreamIdentity::new(A::aggregate_type(), aggregate_id)
20}
21
22pub trait AggregateBuilder: Sized {
24 fn aggregate<A: Aggregate>(self) -> AggregateRepository<Self, A> {
25 AggregateRepository::new(self)
26 }
27}
28
29impl<T> AggregateBuilder for T {}
30
31pub(crate) struct SnapshotPolicy<R, A> {
40 frequency: u64,
42 record: fn(&A, u64) -> Result<Option<SnapshotRecord>, RepositoryError>,
44 hydrate: HydrateFn<R, A>,
47 hydrate_all: HydrateAllFn<R, A>,
50 load: LoadFn<R, A>,
54}
55
56type HydrateFn<R, A> =
57 for<'a> fn(
58 &'a R,
59 &'a StreamIdentity,
60 Entity,
61 ) -> Pin<Box<dyn Future<Output = Result<A, RepositoryError>> + Send + 'a>>;
62
63type HydrateAllFn<R, A> =
64 for<'a> fn(
65 &'a R,
66 Vec<(StreamIdentity, Entity)>,
67 ) -> Pin<Box<dyn Future<Output = Result<Vec<A>, RepositoryError>> + Send + 'a>>;
68
69type LoadFn<R, A> = for<'a> fn(
70 &'a R,
71 &'a StreamIdentity,
72) -> Pin<
73 Box<dyn Future<Output = Result<Option<A>, RepositoryError>> + Send + 'a>,
74>;
75
76impl<R, A> SnapshotPolicy<R, A> {
77 pub(crate) fn new(
80 frequency: u64,
81 record: fn(&A, u64) -> Result<Option<SnapshotRecord>, RepositoryError>,
82 hydrate: HydrateFn<R, A>,
83 hydrate_all: HydrateAllFn<R, A>,
84 load: LoadFn<R, A>,
85 ) -> Self {
86 Self {
87 frequency,
88 record,
89 hydrate,
90 hydrate_all,
91 load,
92 }
93 }
94}
95
96pub struct AggregateRepository<R, A> {
104 repo: R,
105 snapshot: Option<SnapshotPolicy<R, A>>,
106 outbox_publisher: Option<OutboxPublisherConfig>,
107 _marker: PhantomData<A>,
108}
109
110impl<R, A> AggregateRepository<R, A> {
111 pub fn new(repo: R) -> Self {
112 Self {
113 repo,
114 snapshot: None,
115 outbox_publisher: None,
116 _marker: PhantomData,
117 }
118 }
119
120 pub fn repo(&self) -> &R {
121 &self.repo
122 }
123
124 pub fn repo_mut(&mut self) -> &mut R {
125 &mut self.repo
126 }
127
128 pub(crate) fn set_snapshot_policy(&mut self, policy: SnapshotPolicy<R, A>) {
130 self.snapshot = Some(policy);
131 }
132
133 pub(crate) fn set_outbox_publisher(&mut self, config: OutboxPublisherConfig) {
136 self.outbox_publisher = Some(config);
137 }
138
139 pub(crate) fn outbox_publisher(&self) -> Option<&OutboxPublisherConfig> {
142 self.outbox_publisher.as_ref()
143 }
144}
145
146impl<R, A> AggregateRepository<R, A>
147where
148 A: Aggregate + Send,
149{
150 async fn hydrate_entity(
154 &self,
155 identity: &StreamIdentity,
156 entity: Entity,
157 ) -> Result<A, RepositoryError> {
158 match &self.snapshot {
159 Some(policy) => (policy.hydrate)(&self.repo, identity, entity).await,
160 None => hydrate::<A>(entity),
161 }
162 }
163
164 fn snapshot_writes(
168 &self,
169 aggregate: &A,
170 ) -> Result<(Vec<SnapshotWrite>, Option<u64>), RepositoryError> {
171 let Some(policy) = &self.snapshot else {
172 return Ok((Vec::new(), None));
173 };
174 let Some(record) = (policy.record)(aggregate, policy.frequency)? else {
175 return Ok((Vec::new(), None));
176 };
177 let version = record.version;
178 let identity = stream_identity_for::<A>(aggregate.entity().id())?;
179 Ok((
180 vec![SnapshotWrite::Save { identity, record }],
181 Some(version),
182 ))
183 }
184
185 pub(crate) fn snapshot_writes_for(
188 &self,
189 aggregate: &A,
190 ) -> Result<(Vec<SnapshotWrite>, Option<u64>), RepositoryError> {
191 self.snapshot_writes(aggregate)
192 }
193}
194
195impl<R, A> AggregateRepository<R, A>
196where
197 R: GetStream,
198 A: Aggregate + Send,
199{
200 pub async fn get(&self, id: &str) -> Result<Option<A>, RepositoryError> {
201 let identity = stream_identity_for::<A>(id)?;
202 match &self.snapshot {
207 Some(policy) => (policy.load)(&self.repo, &identity).await,
208 None => {
209 let Some(entity) = self.repo.get_stream(&identity).await? else {
210 return Ok(None);
211 };
212 Ok(Some(hydrate::<A>(entity)?))
213 }
214 }
215 }
216
217 pub async fn get_all(&self, ids: &[&str]) -> Result<Vec<A>, RepositoryError> {
224 let identities = ids
225 .iter()
226 .map(|id| stream_identity_for::<A>(id))
227 .collect::<Result<Vec<_>, _>>()?;
228 let entities = self.repo.get_streams(&identities).await?;
229 self.hydrate_entities(entities).await
230 }
231}
232
233impl<R, A> AggregateRepository<R, A>
234where
235 A: Aggregate + Send,
236{
237 async fn hydrate_entities(&self, entities: Vec<Entity>) -> Result<Vec<A>, RepositoryError> {
241 match &self.snapshot {
242 Some(policy) => {
243 let mut pairs = Vec::with_capacity(entities.len());
244 for entity in entities {
245 let identity = stream_identity_for::<A>(entity.id())?;
246 pairs.push((identity, entity));
247 }
248 (policy.hydrate_all)(&self.repo, pairs).await
249 }
250 None => entities.into_iter().map(hydrate::<A>).collect(),
251 }
252 }
253}
254
255impl<R, A> AggregateRepository<R, A>
256where
257 R: TransactionalCommit,
258 A: Aggregate + Send,
259{
260 pub async fn commit(&self, aggregate: &mut A) -> Result<(), RepositoryError> {
261 let (snapshots, snapshot_version) = self.snapshot_writes(aggregate)?;
262 let identity = stream_identity_for::<A>(aggregate.entity().id())?;
263 let stream = StreamWrite::new(identity, aggregate.entity_mut());
264 let mut batch = CommitBatch::new(vec![stream]);
265 batch.snapshots = snapshots;
266 self.repo.commit_batch(batch).await?;
267 if let Some(version) = snapshot_version {
268 aggregate.entity_mut().set_snapshot_version(version);
269 }
270 Ok(())
271 }
272
273 pub async fn commit_all(&self, aggregates: &mut [&mut A]) -> Result<(), RepositoryError> {
274 let mut snapshots = Vec::new();
277 let mut snapshot_versions = Vec::with_capacity(aggregates.len());
278 for aggregate in aggregates.iter() {
279 let (mut writes, version) = self.snapshot_writes(aggregate)?;
280 snapshots.append(&mut writes);
281 snapshot_versions.push(version);
282 }
283
284 let mut streams = Vec::with_capacity(aggregates.len());
285 for aggregate in aggregates.iter_mut() {
286 let identity = stream_identity_for::<A>((*aggregate).entity().id())?;
287 streams.push(StreamWrite::new(identity, (*aggregate).entity_mut()));
288 }
289 let mut batch = CommitBatch::new(streams);
290 batch.snapshots = snapshots;
291 self.repo.commit_batch(batch).await?;
292
293 for (aggregate, version) in aggregates.iter_mut().zip(snapshot_versions) {
294 if let Some(version) = version {
295 (*aggregate).entity_mut().set_snapshot_version(version);
296 }
297 }
298 Ok(())
299 }
300
301 pub async fn commit_entities(
302 &self,
303 streams: Vec<(StreamIdentity, &mut Entity)>,
304 ) -> Result<(), RepositoryError> {
305 let streams = streams
306 .into_iter()
307 .map(|(identity, entity)| StreamWrite::new(identity, entity))
308 .collect();
309 self.repo.commit_batch(CommitBatch::new(streams)).await
310 }
311}
312
313impl<R, A> AggregateRepository<R, A>
314where
315 R: GetWithOpts,
316 A: Aggregate + Send,
317{
318 pub async fn get_with(&self, id: &str, opts: ReadOpts) -> Result<Option<A>, RepositoryError> {
321 let identity = stream_identity_for::<A>(id)?;
322 let Some(entity) = self.repo.get_stream_with(&identity, opts).await? else {
323 return Ok(None);
324 };
325 Ok(Some(self.hydrate_entity(&identity, entity).await?))
326 }
327
328 pub async fn peek(&self, id: &str) -> Result<Option<A>, RepositoryError> {
330 self.get_with(id, ReadOpts::no_lock()).await
331 }
332}
333
334impl<R, A> AggregateRepository<R, A>
335where
336 R: GetAllWithOpts,
337 A: Aggregate + Send,
338{
339 pub async fn get_all_with(
341 &self,
342 ids: &[&str],
343 opts: ReadOpts,
344 ) -> Result<Vec<A>, RepositoryError> {
345 let identities = ids
346 .iter()
347 .map(|id| stream_identity_for::<A>(id))
348 .collect::<Result<Vec<_>, _>>()?;
349 let entities = self.repo.get_streams_with(&identities, opts).await?;
350 self.hydrate_entities(entities).await
351 }
352
353 pub async fn peek_all(&self, ids: &[&str]) -> Result<Vec<A>, RepositoryError> {
355 self.get_all_with(ids, ReadOpts::no_lock()).await
356 }
357}
358
359impl<R, A> AggregateRepository<R, A>
360where
361 R: UnlockableRepository,
362 A: Aggregate,
363{
364 pub async fn abort(&self, aggregate: &A) -> Result<(), RepositoryError> {
366 let identity = stream_identity_for::<A>(aggregate.entity().id())?;
367 self.repo.abort(&identity).await
371 }
372
373 pub async fn unlock(&self, id: &str) -> Result<(), RepositoryError> {
375 let identity = stream_identity_for::<A>(id)?;
376 self.repo.unlock(&identity).await
377 }
378}