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>,
46}
47
48type HydrateFn<R, A> =
49 for<'a> fn(
50 &'a R,
51 &'a StreamIdentity,
52 Entity,
53 ) -> Pin<Box<dyn Future<Output = Result<A, RepositoryError>> + Send + 'a>>;
54
55impl<R, A> SnapshotPolicy<R, A> {
56 pub(crate) fn new(
59 frequency: u64,
60 record: fn(&A, u64) -> Result<Option<SnapshotRecord>, RepositoryError>,
61 hydrate: HydrateFn<R, A>,
62 ) -> Self {
63 Self {
64 frequency,
65 record,
66 hydrate,
67 }
68 }
69}
70
71pub struct AggregateRepository<R, A> {
79 repo: R,
80 snapshot: Option<SnapshotPolicy<R, A>>,
81 outbox_publisher: Option<OutboxPublisherConfig>,
82 _marker: PhantomData<A>,
83}
84
85impl<R, A> AggregateRepository<R, A> {
86 pub fn new(repo: R) -> Self {
87 Self {
88 repo,
89 snapshot: None,
90 outbox_publisher: None,
91 _marker: PhantomData,
92 }
93 }
94
95 pub fn repo(&self) -> &R {
96 &self.repo
97 }
98
99 pub fn repo_mut(&mut self) -> &mut R {
100 &mut self.repo
101 }
102
103 pub(crate) fn set_snapshot_policy(&mut self, policy: SnapshotPolicy<R, A>) {
105 self.snapshot = Some(policy);
106 }
107
108 pub(crate) fn set_outbox_publisher(&mut self, config: OutboxPublisherConfig) {
111 self.outbox_publisher = Some(config);
112 }
113
114 pub(crate) fn outbox_publisher(&self) -> Option<&OutboxPublisherConfig> {
117 self.outbox_publisher.as_ref()
118 }
119}
120
121impl<R, A> AggregateRepository<R, A>
122where
123 A: Aggregate + Send,
124{
125 async fn hydrate_entity(
129 &self,
130 identity: &StreamIdentity,
131 entity: Entity,
132 ) -> Result<A, RepositoryError> {
133 match &self.snapshot {
134 Some(policy) => (policy.hydrate)(&self.repo, identity, entity).await,
135 None => hydrate::<A>(entity),
136 }
137 }
138
139 fn snapshot_writes(
143 &self,
144 aggregate: &A,
145 ) -> Result<(Vec<SnapshotWrite>, Option<u64>), RepositoryError> {
146 let Some(policy) = &self.snapshot else {
147 return Ok((Vec::new(), None));
148 };
149 let Some(record) = (policy.record)(aggregate, policy.frequency)? else {
150 return Ok((Vec::new(), None));
151 };
152 let version = record.version;
153 let identity = stream_identity_for::<A>(aggregate.entity().id())?;
154 Ok((
155 vec![SnapshotWrite::Save { identity, record }],
156 Some(version),
157 ))
158 }
159
160 pub(crate) fn snapshot_writes_for(
163 &self,
164 aggregate: &A,
165 ) -> Result<(Vec<SnapshotWrite>, Option<u64>), RepositoryError> {
166 self.snapshot_writes(aggregate)
167 }
168}
169
170impl<R, A> AggregateRepository<R, A>
171where
172 R: GetStream,
173 A: Aggregate + Send,
174{
175 pub async fn get(&self, id: &str) -> Result<Option<A>, RepositoryError> {
176 let identity = stream_identity_for::<A>(id)?;
177 let entity = self.repo.get_stream(&identity).await?;
178 let Some(entity) = entity else {
179 return Ok(None);
180 };
181 Ok(Some(self.hydrate_entity(&identity, entity).await?))
182 }
183
184 pub async fn get_all(&self, ids: &[&str]) -> Result<Vec<A>, RepositoryError> {
191 let identities = ids
192 .iter()
193 .map(|id| stream_identity_for::<A>(id))
194 .collect::<Result<Vec<_>, _>>()?;
195 let entities = self.repo.get_streams(&identities).await?;
196 self.hydrate_entities(entities).await
197 }
198}
199
200impl<R, A> AggregateRepository<R, A>
201where
202 A: Aggregate + Send,
203{
204 async fn hydrate_entities(&self, entities: Vec<Entity>) -> Result<Vec<A>, RepositoryError> {
207 let mut aggregates = Vec::with_capacity(entities.len());
208 for entity in entities {
209 let identity = stream_identity_for::<A>(entity.id())?;
210 aggregates.push(self.hydrate_entity(&identity, entity).await?);
211 }
212 Ok(aggregates)
213 }
214}
215
216impl<R, A> AggregateRepository<R, A>
217where
218 R: TransactionalCommit,
219 A: Aggregate + Send,
220{
221 pub async fn commit(&self, aggregate: &mut A) -> Result<(), RepositoryError> {
222 let (snapshots, snapshot_version) = self.snapshot_writes(aggregate)?;
223 let identity = stream_identity_for::<A>(aggregate.entity().id())?;
224 let stream = StreamWrite::new(identity, aggregate.entity_mut());
225 let mut batch = CommitBatch::new(vec![stream]);
226 batch.snapshots = snapshots;
227 self.repo.commit_batch(batch).await?;
228 if let Some(version) = snapshot_version {
229 aggregate.entity_mut().set_snapshot_version(version);
230 }
231 Ok(())
232 }
233
234 pub async fn commit_all(&self, aggregates: &mut [&mut A]) -> Result<(), RepositoryError> {
235 let mut snapshots = Vec::new();
238 let mut snapshot_versions = Vec::with_capacity(aggregates.len());
239 for aggregate in aggregates.iter() {
240 let (mut writes, version) = self.snapshot_writes(aggregate)?;
241 snapshots.append(&mut writes);
242 snapshot_versions.push(version);
243 }
244
245 let mut streams = Vec::with_capacity(aggregates.len());
246 for aggregate in aggregates.iter_mut() {
247 let identity = stream_identity_for::<A>((*aggregate).entity().id())?;
248 streams.push(StreamWrite::new(identity, (*aggregate).entity_mut()));
249 }
250 let mut batch = CommitBatch::new(streams);
251 batch.snapshots = snapshots;
252 self.repo.commit_batch(batch).await?;
253
254 for (aggregate, version) in aggregates.iter_mut().zip(snapshot_versions) {
255 if let Some(version) = version {
256 (*aggregate).entity_mut().set_snapshot_version(version);
257 }
258 }
259 Ok(())
260 }
261
262 pub async fn commit_entities(
263 &self,
264 streams: Vec<(StreamIdentity, &mut Entity)>,
265 ) -> Result<(), RepositoryError> {
266 let streams = streams
267 .into_iter()
268 .map(|(identity, entity)| StreamWrite::new(identity, entity))
269 .collect();
270 self.repo.commit_batch(CommitBatch::new(streams)).await
271 }
272}
273
274impl<R, A> AggregateRepository<R, A>
275where
276 R: GetWithOpts,
277 A: Aggregate + Send,
278{
279 pub async fn get_with(&self, id: &str, opts: ReadOpts) -> Result<Option<A>, RepositoryError> {
282 let identity = stream_identity_for::<A>(id)?;
283 let Some(entity) = self.repo.get_stream_with(&identity, opts).await? else {
284 return Ok(None);
285 };
286 Ok(Some(self.hydrate_entity(&identity, entity).await?))
287 }
288
289 pub async fn peek(&self, id: &str) -> Result<Option<A>, RepositoryError> {
291 self.get_with(id, ReadOpts::no_lock()).await
292 }
293}
294
295impl<R, A> AggregateRepository<R, A>
296where
297 R: GetAllWithOpts,
298 A: Aggregate + Send,
299{
300 pub async fn get_all_with(
302 &self,
303 ids: &[&str],
304 opts: ReadOpts,
305 ) -> Result<Vec<A>, RepositoryError> {
306 let identities = ids
307 .iter()
308 .map(|id| stream_identity_for::<A>(id))
309 .collect::<Result<Vec<_>, _>>()?;
310 let entities = self.repo.get_streams_with(&identities, opts).await?;
311 self.hydrate_entities(entities).await
312 }
313
314 pub async fn peek_all(&self, ids: &[&str]) -> Result<Vec<A>, RepositoryError> {
316 self.get_all_with(ids, ReadOpts::no_lock()).await
317 }
318}
319
320impl<R, A> AggregateRepository<R, A>
321where
322 R: UnlockableRepository,
323 A: Aggregate,
324{
325 pub async fn abort(&self, aggregate: &A) -> Result<(), RepositoryError> {
327 let identity = stream_identity_for::<A>(aggregate.entity().id())?;
328 self.repo.abort(&identity).await
332 }
333
334 pub async fn unlock(&self, id: &str) -> Result<(), RepositoryError> {
336 let identity = stream_identity_for::<A>(id)?;
337 self.repo.unlock(&identity).await
338 }
339}