Skip to main content

mongo_es/
event_repository.rs

1use cqrs_es::{
2    persist::{
3        PersistedEventRepository, PersistenceError, ReplayStream, SerializedEvent,
4        SerializedSnapshot,
5    },
6    Aggregate,
7};
8use futures::StreamExt;
9use mongodb::{
10    bson::{self, doc, Document},
11    options::IndexOptions,
12    Cursor, IndexModel,
13};
14use mongodb::{options::FindOptions, Client, Collection};
15use serde_json::Value;
16
17use crate::error::MongoAggregateError;
18
19const DEFAULT_EVENT_COLLECTION: &str = "events";
20const DEFAULT_SNAPSHOT_COLLECTION: &str = "snapshots";
21
22const DEFAULT_STREAMING_CHANNEL_SIZE: usize = 200;
23
24/// An event repository using MongoDB for persistence.
25pub struct MongoEventRepository {
26    client: Client,
27    event_collection: String,
28    snapshot_collection: String,
29    stream_channel_size: usize,
30}
31
32impl MongoEventRepository {
33    /// Creates a new `MongoEventRepository` with the default collection names and default streaming channel size.
34    pub async fn new(client: Client) -> mongodb::error::Result<Self> {
35        let repository = Self {
36            client,
37            event_collection: DEFAULT_EVENT_COLLECTION.to_string(),
38            snapshot_collection: DEFAULT_SNAPSHOT_COLLECTION.to_string(),
39            stream_channel_size: DEFAULT_STREAMING_CHANNEL_SIZE,
40        };
41        // TODO: is this the right place to create indexes?
42        repository.create_indexes().await?;
43        Ok(repository)
44    }
45
46    /// Configures a `MongoEventRepository` to use custom collection names.
47    ///
48    /// Defaults:
49    /// - `event_collection`: "events"
50    /// - `snapshot_collection`: "snapshots"
51    pub fn with_collection_names(self, event_collection: &str, snapshot_collection: &str) -> Self {
52        Self {
53            client: self.client,
54            event_collection: event_collection.to_string(),
55            snapshot_collection: snapshot_collection.to_string(),
56            stream_channel_size: self.stream_channel_size,
57        }
58    }
59
60    /// Configures a `MongoEventRepository` to use a custom streaming channel size.
61    ///
62    /// Default: `200`
63    pub fn with_streaming_channel_size(self, stream_channel_size: usize) -> Self {
64        Self {
65            client: self.client,
66            event_collection: self.event_collection,
67            snapshot_collection: self.snapshot_collection,
68            stream_channel_size,
69        }
70    }
71
72    // helper: get collection by name from default database
73    fn get_collection(&self, name: &str) -> Collection<Document> {
74        self.client
75            .default_database()
76            .expect("Default database not configured")
77            .collection::<Document>(name)
78    }
79
80    /// Creates indexes for the event and snapshot collections to ensure uniqueness.
81    async fn create_indexes(&self) -> mongodb::error::Result<()> {
82        let event_collection = self.get_collection(&self.event_collection);
83
84        let index = IndexModel::builder()
85            .keys(doc! { "aggregate_id": 1, "sequence": 1 })
86            .options(
87                IndexOptions::builder()
88                    .unique(true)
89                    // .name("aggregate_id_sequence_unique".to_string())
90                    .build(),
91            )
92            .build();
93
94        event_collection.create_index(index).await?;
95
96        log::debug!(
97            "Created compound index on `{}` collection",
98            self.event_collection
99        );
100
101        let snapshot_collection = self.get_collection(&self.snapshot_collection);
102
103        let index = IndexModel::builder()
104            .keys(doc! { "aggregate_id": 1, "current_snapshot": -1 })
105            .options(
106                IndexOptions::builder()
107                    .unique(true)
108                    // .name("aggregate_id_current_snapshot_unique".to_string())
109                    .build(),
110            )
111            .build();
112
113        snapshot_collection.create_index(index).await?;
114
115        log::debug!(
116            "Created compound index on `{}` collection",
117            self.snapshot_collection
118        );
119
120        Ok(())
121    }
122
123    pub(crate) async fn insert_events(
124        &self,
125        events: &[SerializedEvent],
126    ) -> Result<(), MongoAggregateError> {
127        if events.is_empty() {
128            return Ok(());
129        }
130
131        let collection: Collection<Document> = self.get_collection(&self.event_collection);
132
133        let (documents, _) = Self::build_event_upsert_documents(events);
134
135        // Running transactions in a session ensures atomicity.
136        let mut session = self.client.start_session().await?;
137        session.start_transaction().await?;
138        let res = collection
139            .insert_many(documents)
140            .session(&mut session)
141            .await?;
142        session.commit_transaction().await?;
143
144        log::debug!(
145            "Inserted {} events into `{}` collection",
146            res.inserted_ids.len(),
147            self.event_collection
148        );
149
150        Ok(())
151    }
152
153    // helper: transforms serialized events into MongoDB documents
154    fn build_event_upsert_documents(events: &[SerializedEvent]) -> (Vec<Document>, usize) {
155        let mut current_sequence: usize = 0;
156        let mut documents: Vec<Document> = Vec::default();
157        for event in events {
158            current_sequence = event.sequence;
159            documents.push(doc! {
160                "aggregate_id": event.aggregate_id.clone(),
161                "aggregate_type": event.aggregate_type.clone(),
162                "sequence": event.sequence as i64,
163                "event_type": event.event_type.clone(),
164                "event_version": event.event_version.clone(),
165                "payload": bson::to_bson(&event.payload).unwrap(),
166                "metadata": bson::to_bson(&event.metadata).unwrap(),
167            });
168        }
169        (documents, current_sequence)
170    }
171
172    /// Returns all events for the given aggregate type and id, starting from the specified sequence.
173    async fn query_events(
174        &self,
175        aggregate_type: &str,
176        aggregate_id: &str,
177        min_sequence: usize,
178    ) -> Result<Vec<SerializedEvent>, MongoAggregateError> {
179        let mut cursor = self
180            .query_collection(
181                aggregate_type,
182                aggregate_id,
183                &self.event_collection,
184                min_sequence as i64,
185                None,
186            )
187            .await?;
188        let mut events: Vec<SerializedEvent> = Default::default();
189        while cursor.advance().await? {
190            let document = cursor.deserialize_current()?;
191            events.push(serialized_event(&document)?);
192        }
193        Ok(events)
194    }
195
196    pub(crate) async fn update_snapshot<A: Aggregate>(
197        &self,
198        aggregate_payload: Value,
199        aggregate_id: String,
200        current_snapshot: usize,
201        events: &[SerializedEvent],
202    ) -> Result<(), MongoAggregateError> {
203        let (_, current_sequence) = Self::build_event_upsert_documents(events);
204        self.insert_events(events).await?;
205
206        let collection: Collection<Document> = self.get_collection(&self.snapshot_collection);
207
208        let expected_snapshot = current_snapshot - 1;
209
210        let snapshot = doc! {
211            "aggregate_type": A::TYPE,
212            "aggregate_id": &aggregate_id,
213            "payload": bson::to_bson(&aggregate_payload).unwrap(),
214            "current_sequence": current_sequence as i64,
215            "current_snapshot": current_snapshot as i64,
216        };
217
218        let filter = doc! {
219            "aggregate_id": &aggregate_id,
220            "aggregate_type": A::TYPE,
221            "current_snapshot": expected_snapshot as i64,
222        };
223
224        // Replaces the snapshot entirely if it exists instead of patching it.
225        let res = collection
226            .replace_one(filter, snapshot)
227            .upsert(true)
228            .await
229            .map_err(MongoAggregateError::from)?;
230
231        if res.matched_count == 0 && res.upserted_id.is_none() {
232            return Err(MongoAggregateError::OptimisticLock);
233        }
234
235        log::debug!(
236            "Inserted snapshot for `{}` with id `{}`",
237            A::TYPE,
238            &aggregate_id
239        );
240
241        Ok(())
242    }
243
244    /// Queries the MongoDB collection and returns a cursor.
245    async fn query_collection(
246        &self,
247        aggregate_type: &str,
248        aggregate_id: &str,
249        collection: &str,
250        min_sequence: i64,
251        sort: Option<Document>,
252    ) -> Result<Cursor<Document>, MongoAggregateError> {
253        let filter = self.build_filter(aggregate_type, aggregate_id, min_sequence);
254        let options = FindOptions::builder().sort(sort).build();
255        let cursor = self
256            .get_collection(collection)
257            .find(filter)
258            .with_options(options)
259            .await?;
260        Ok(cursor)
261    }
262
263    fn build_filter(
264        &self,
265        aggregate_type: &str,
266        aggregate_id: &str,
267        min_sequence: i64,
268    ) -> Document {
269        if min_sequence == 0 {
270            doc! {
271                "aggregate_type": aggregate_type,
272                "aggregate_id": aggregate_id,
273            }
274        } else {
275            doc! {
276                "aggregate_type": aggregate_type,
277                "aggregate_id": aggregate_id,
278                "sequence": { "$gte": min_sequence },
279            }
280        }
281    }
282}
283
284fn serialized_event(document: &Document) -> Result<SerializedEvent, MongoAggregateError> {
285    let aggregate_id = document.get_str("aggregate_id")?.to_string();
286    let sequence = document.get_i64("sequence")? as usize;
287    let aggregate_type = document.get_str("aggregate_type")?.to_string();
288    let event_type = document.get_str("event_type")?.to_string();
289    let event_version = document.get_str("event_version")?.to_string();
290    let payload = bson::from_bson(document.get("payload").into())?;
291    let metadata = bson::from_bson(document.get("metadata").into())?;
292
293    Ok(SerializedEvent {
294        aggregate_id,
295        sequence,
296        aggregate_type,
297        event_type,
298        event_version,
299        payload,
300        metadata,
301    })
302}
303
304impl PersistedEventRepository for MongoEventRepository {
305    async fn get_events<A: Aggregate>(
306        &self,
307        aggregate_id: &str,
308    ) -> Result<Vec<SerializedEvent>, PersistenceError> {
309        let events = self.query_events(A::TYPE, aggregate_id, 0).await?;
310        Ok(events)
311    }
312
313    async fn get_last_events<A: Aggregate>(
314        &self,
315        aggregate_id: &str,
316        last_sequence: usize,
317    ) -> Result<Vec<SerializedEvent>, PersistenceError> {
318        let events = self
319            .query_events(A::TYPE, aggregate_id, last_sequence)
320            .await?;
321        Ok(events)
322    }
323
324    async fn get_snapshot<A: Aggregate>(
325        &self,
326        aggregate_id: &str,
327    ) -> Result<Option<SerializedSnapshot>, PersistenceError> {
328        let mut cursor = self
329            .query_collection(
330                A::TYPE,
331                aggregate_id,
332                &self.snapshot_collection,
333                0,
334                Some(doc! { "current_snapshot": -1 }), // sort descending
335            )
336            .await?;
337
338        if let Some(result) = cursor.next().await {
339            let document = result.map_err(MongoAggregateError::from)?;
340            let payload = bson::from_bson(document.get("payload").unwrap().clone()).unwrap();
341            log::debug!(
342                "Found snapshot for `{}` with id `{}`",
343                A::TYPE,
344                aggregate_id
345            );
346            Ok(Some(SerializedSnapshot {
347                aggregate_id: aggregate_id.to_string(),
348                aggregate: payload,
349                current_sequence: document
350                    .get_i64("current_sequence")
351                    .map_err(MongoAggregateError::from)? as usize,
352                current_snapshot: document
353                    .get_i64("current_snapshot")
354                    .map_err(MongoAggregateError::from)? as usize,
355            }))
356        } else {
357            Ok(None)
358        }
359    }
360
361    async fn persist<A: Aggregate>(
362        &self,
363        events: &[SerializedEvent],
364        snapshot_update: Option<(String, Value, usize)>,
365    ) -> Result<(), PersistenceError> {
366        match snapshot_update {
367            None => {
368                self.insert_events(events).await?;
369            }
370            Some((aggregate_id, aggregate_payload, current_snapshot)) => {
371                self.update_snapshot::<A>(
372                    aggregate_payload,
373                    aggregate_id,
374                    current_snapshot,
375                    events,
376                )
377                .await?;
378            }
379        }
380        Ok(())
381    }
382
383    async fn stream_events<A: Aggregate>(
384        &self,
385        aggregate_id: &str,
386    ) -> Result<ReplayStream, PersistenceError> {
387        let query = self
388            .query_collection(A::TYPE, aggregate_id, &self.event_collection, 0, None)
389            .await?;
390        Ok(stream_events(query, self.stream_channel_size))
391    }
392
393    async fn stream_all_events<A: Aggregate>(&self) -> Result<ReplayStream, PersistenceError> {
394        let filter = doc! { "aggregate_type": A::TYPE };
395        let options = FindOptions::builder().sort(doc! { "sequence": 1 }).build();
396        let cursor = self
397            .get_collection(&self.event_collection)
398            .find(filter)
399            .with_options(options)
400            .await
401            .unwrap();
402        Ok(stream_events(cursor, self.stream_channel_size))
403    }
404}
405
406fn stream_events(mut cursor: Cursor<Document>, channel_size: usize) -> ReplayStream {
407    let (mut feed, stream) = ReplayStream::new(channel_size);
408    tokio::spawn(async move {
409        while let Some(result) = cursor.next().await {
410            match result {
411                Ok(document) => {
412                    if let Ok(event) = serialized_event(&document) {
413                        if feed.push(Ok(event)).await.is_err() {
414                            log::warn!("Could not push event to feed. Stopping event stream.");
415                            return;
416                        };
417                    } else {
418                        log::error!("Failed to deserialize event: {:?}", document);
419                    }
420                }
421                Err(e) => {
422                    log::error!("Error while streaming events: {}", e);
423                    let e: MongoAggregateError = e.into();
424                    if feed.push(Err(e.into())).await.is_err() {
425                        log::warn!("Could not push error to feed. Stopping event stream.");
426                        return;
427                    }
428                }
429            }
430        }
431    });
432    stream
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    use cqrs_es::doc::{Customer, CustomerEvent};
440
441    use crate::utils::tests::{mongodb_client, test_event, test_snapshot_context};
442
443    #[tokio::test]
444    async fn test_event_repository_inserts_successfully() {
445        let client = mongodb_client().await;
446        let repository = MongoEventRepository::new(client)
447            .await
448            .unwrap()
449            .with_streaming_channel_size(1);
450        let aggregate_id = uuid::Uuid::new_v4().to_string();
451        let events = repository
452            .get_events::<Customer>(&aggregate_id)
453            .await
454            .unwrap();
455        assert!(events.is_empty());
456
457        // Insert events
458        repository
459            .insert_events(&[
460                test_event(
461                    &aggregate_id,
462                    1,
463                    CustomerEvent::NameAdded {
464                        name: "Ferris".to_string(),
465                    },
466                ),
467                test_event(
468                    &aggregate_id,
469                    2,
470                    CustomerEvent::EmailUpdated {
471                        new_email: "ferris@example.test".to_string(),
472                    },
473                ),
474            ])
475            .await
476            .unwrap();
477        let events = repository
478            .get_events::<Customer>(&aggregate_id)
479            .await
480            .unwrap();
481        assert_eq!(2, events.len());
482        events
483            .iter()
484            .for_each(|e| assert_eq!(&aggregate_id, &e.aggregate_id));
485    }
486
487    #[tokio::test]
488    async fn test_event_repository_get_last_events() {
489        let client = mongodb_client().await;
490        let repository = MongoEventRepository::new(client)
491            .await
492            .unwrap()
493            .with_streaming_channel_size(1);
494        let aggregate_id = uuid::Uuid::new_v4().to_string();
495        let events = repository
496            .get_events::<Customer>(&aggregate_id)
497            .await
498            .unwrap();
499        assert!(events.is_empty());
500
501        // Insert events
502        repository
503            .insert_events(&[
504                test_event(
505                    &aggregate_id,
506                    1,
507                    CustomerEvent::NameAdded {
508                        name: "Ferris".to_string(),
509                    },
510                ),
511                test_event(
512                    &aggregate_id,
513                    3,
514                    CustomerEvent::EmailUpdated {
515                        new_email: "second@example.test".to_string(),
516                    },
517                ),
518                test_event(
519                    &aggregate_id,
520                    2,
521                    CustomerEvent::EmailUpdated {
522                        new_email: "first@example.test".to_string(),
523                    },
524                ),
525                test_event(
526                    &aggregate_id,
527                    4,
528                    CustomerEvent::NameAdded {
529                        name: "F3rr1s".to_string(),
530                    },
531                ),
532            ])
533            .await
534            .unwrap();
535
536        let events = repository
537            .get_last_events::<Customer>(&aggregate_id, 3)
538            .await
539            .unwrap();
540        assert_eq!(2, events.len());
541        assert_eq!(3, events[0].sequence);
542        assert_eq!(4, events[1].sequence);
543    }
544
545    #[tokio::test]
546    async fn test_event_repository_invalid_sequence_throws_error() {
547        let client = mongodb_client().await;
548        let repository = MongoEventRepository::new(client)
549            .await
550            .unwrap()
551            .with_streaming_channel_size(1);
552        let aggregate_id = uuid::Uuid::new_v4().to_string();
553
554        // Insert event
555        repository
556            .insert_events(&[test_event(
557                &aggregate_id,
558                1,
559                CustomerEvent::EmailUpdated {
560                    new_email: "first@example.test".to_string(),
561                },
562            )])
563            .await
564            .unwrap();
565
566        // Transaction should fail if any of the events has invalid sequence number
567        let result = repository
568            .insert_events(&[
569                test_event(
570                    &aggregate_id,
571                    2,
572                    CustomerEvent::EmailUpdated {
573                        new_email: "second@example.test".to_string(),
574                    },
575                ),
576                test_event(
577                    &aggregate_id,
578                    1,
579                    CustomerEvent::NameAdded {
580                        name: "Ferris".to_string(),
581                    },
582                ),
583            ])
584            .await
585            .unwrap_err();
586
587        match result {
588            MongoAggregateError::OptimisticLock => {}
589            _ => panic!("Expected OptimisticLockError, got {result:?}"),
590        }
591
592        let events = repository
593            .get_events::<Customer>(&aggregate_id)
594            .await
595            .unwrap();
596        assert_eq!(1, events.len());
597    }
598
599    #[tokio::test]
600    async fn test_event_repository_replay_stream_aggregate_instance() {
601        let client = mongodb_client().await;
602        let repository = MongoEventRepository::new(client)
603            .await
604            .unwrap()
605            .with_streaming_channel_size(3);
606        let aggregate_id = uuid::Uuid::new_v4().to_string();
607
608        // Create 10 test events
609        let events: Vec<SerializedEvent> = (1..=10)
610            .map(|i| {
611                test_event(
612                    &aggregate_id,
613                    i,
614                    CustomerEvent::EmailUpdated {
615                        new_email: format!("{i}@example.test").to_string(),
616                    },
617                )
618            })
619            .collect();
620        repository.insert_events(&events).await.unwrap();
621
622        let mut stream = repository
623            .stream_events::<Customer>(&aggregate_id)
624            .await
625            .unwrap();
626        let mut num_events = 0;
627        while (stream.next::<Customer>(&[]).await).is_some() {
628            num_events += 1;
629        }
630        assert_eq!(10, num_events);
631    }
632
633    #[tokio::test]
634    async fn test_event_repository_replay_stream_all_aggregate_type() {
635        let client = mongodb_client().await;
636        let repository = MongoEventRepository::new(client)
637            .await
638            .unwrap()
639            .with_streaming_channel_size(3);
640        let aggregate_ids: Vec<String> = (0..3).map(|_| uuid::Uuid::new_v4().to_string()).collect();
641
642        clear_collection(repository.get_collection(&repository.event_collection)).await;
643
644        // Create 10 test events for each aggregate instance
645        for aggregate_id in &aggregate_ids {
646            let events: Vec<SerializedEvent> = (1..=10)
647                .map(|i| {
648                    test_event(
649                        aggregate_id,
650                        i,
651                        CustomerEvent::EmailUpdated {
652                            new_email: format!("{i}@example.test").to_string(),
653                        },
654                    )
655                })
656                .collect();
657            repository.insert_events(&events).await.unwrap();
658        }
659
660        let mut stream = repository.stream_all_events::<Customer>().await.unwrap();
661        let mut num_events = 0;
662        while (stream.next::<Customer>(&[]).await).is_some() {
663            num_events += 1;
664        }
665        assert_eq!(30, num_events);
666    }
667
668    #[tokio::test]
669    async fn test_snapshot_repository_empty_returns_none() {
670        let client = mongodb_client().await;
671        let repository = MongoEventRepository::new(client)
672            .await
673            .unwrap()
674            .with_streaming_channel_size(1);
675        let aggregate_id = uuid::Uuid::new_v4().to_string();
676
677        let snapshot = repository
678            .get_snapshot::<Customer>(&aggregate_id)
679            .await
680            .unwrap();
681        assert_eq!(None, snapshot);
682    }
683
684    #[tokio::test]
685    async fn test_snapshot_repository_inserts_successfully() {
686        let client = mongodb_client().await;
687        let repository = MongoEventRepository::new(client)
688            .await
689            .unwrap()
690            .with_streaming_channel_size(1);
691        let aggregate_id = uuid::Uuid::new_v4().to_string();
692
693        repository
694            .update_snapshot::<Customer>(
695                serde_json::to_value(Customer {
696                    customer_id: "123".to_string(),
697                    name: "Ferris".to_string(),
698                    email: "email@example.test".to_string(),
699                    data_populated: true,
700                })
701                .unwrap(),
702                aggregate_id.clone(),
703                1,
704                &[],
705            )
706            .await
707            .unwrap();
708
709        let snapshot = repository
710            .get_snapshot::<Customer>(&aggregate_id)
711            .await
712            .unwrap();
713
714        assert_eq!(
715            Some(test_snapshot_context(
716                aggregate_id.clone(),
717                serde_json::to_value(Customer {
718                    customer_id: "123".to_string(),
719                    name: "Ferris".to_string(),
720                    email: "email@example.test".to_string(),
721                    data_populated: true,
722                })
723                .unwrap(),
724                0,
725                1
726            )),
727            snapshot
728        );
729    }
730
731    #[tokio::test]
732    async fn test_snapshot_repository_returns_latest_snapshot() {
733        let client = mongodb_client().await;
734        let repository = MongoEventRepository::new(client)
735            .await
736            .unwrap()
737            .with_streaming_channel_size(1);
738        let aggregate_id = uuid::Uuid::new_v4().to_string();
739
740        // First snapshot
741        repository
742            .update_snapshot::<Customer>(
743                serde_json::to_value(Customer {
744                    customer_id: "123".to_string(),
745                    name: "Ferris".to_string(),
746                    email: "first@example.test".to_string(),
747                    data_populated: true,
748                })
749                .unwrap(),
750                aggregate_id.clone(),
751                1,
752                &[],
753            )
754            .await
755            .unwrap();
756
757        // Second snapshot
758        repository
759            .update_snapshot::<Customer>(
760                serde_json::to_value(Customer {
761                    customer_id: "123".to_string(),
762                    name: "Ferris".to_string(),
763                    email: "second@example.test".to_string(),
764                    data_populated: true,
765                })
766                .unwrap(),
767                aggregate_id.clone(),
768                2,
769                &[],
770            )
771            .await
772            .unwrap();
773
774        let snapshot = repository
775            .get_snapshot::<Customer>(&aggregate_id)
776            .await
777            .unwrap();
778
779        assert_eq!(
780            Some(test_snapshot_context(
781                aggregate_id.clone(),
782                serde_json::to_value(Customer {
783                    customer_id: "123".to_string(),
784                    name: "Ferris".to_string(),
785                    email: "second@example.test".to_string(),
786                    data_populated: true,
787                })
788                .unwrap(),
789                0,
790                2
791            )),
792            snapshot
793        );
794    }
795
796    #[tokio::test]
797    async fn test_snapshot_repository_invalid_sequence_returns_error() {
798        let client = mongodb_client().await;
799        let repository = MongoEventRepository::new(client)
800            .await
801            .unwrap()
802            .with_streaming_channel_size(1);
803        let aggregate_id = uuid::Uuid::new_v4().to_string();
804
805        // First snapshot
806        repository
807            .update_snapshot::<Customer>(
808                serde_json::to_value(Customer {
809                    customer_id: "123".to_string(),
810                    name: "Ferris".to_string(),
811                    email: "first@example.test".to_string(),
812                    data_populated: true,
813                })
814                .unwrap(),
815                aggregate_id.clone(),
816                1,
817                &[],
818            )
819            .await
820            .unwrap();
821
822        // Second snapshot, but with same sequence number
823        let result = repository
824            .update_snapshot::<Customer>(
825                serde_json::to_value(Customer {
826                    customer_id: "123".to_string(),
827                    name: "Ferris".to_string(),
828                    email: "second@example.test".to_string(),
829                    data_populated: true,
830                })
831                .unwrap(),
832                aggregate_id.clone(),
833                1,
834                &[],
835            )
836            .await
837            .unwrap_err();
838
839        match result {
840            MongoAggregateError::OptimisticLock => {}
841            _ => panic!("Expected OptimisticLockError, got {result:?}"),
842        }
843
844        let snapshot = repository
845            .get_snapshot::<Customer>(&aggregate_id)
846            .await
847            .unwrap();
848
849        assert_eq!(
850            Some(test_snapshot_context(
851                aggregate_id.clone(),
852                serde_json::to_value(Customer {
853                    customer_id: "123".to_string(),
854                    name: "Ferris".to_string(),
855                    email: "first@example.test".to_string(),
856                    data_populated: true,
857                })
858                .unwrap(),
859                0,
860                1
861            )),
862            snapshot
863        );
864    }
865
866    /// Deletes all documents in the specified collection.
867    async fn clear_collection(collection: Collection<Document>) {
868        collection.delete_many(doc! {}).await.unwrap();
869    }
870}