mongo_es/
view_repository.rs1use async_trait::async_trait;
2use cqrs_es::persist::{PersistenceError, ViewContext, ViewRepository};
3use cqrs_es::{Aggregate, View};
4use mongodb::bson::{self, doc, Document};
5
6use crate::utils::load_view;
7
8pub struct MongoViewRepository<V, A> {
9 _phantom: std::marker::PhantomData<(V, A)>,
10 view_name: String,
11 client: mongodb::Client,
12}
13
14impl<V, A> MongoViewRepository<V, A>
15where
16 V: View<A>,
17 A: Aggregate,
18{
19 pub fn new(view_name: &str, client: mongodb::Client) -> Self {
20 Self {
21 _phantom: Default::default(),
22 view_name: view_name.to_string(),
23 client,
24 }
25 }
26}
27
28#[async_trait]
29impl<V, A> ViewRepository<V, A> for MongoViewRepository<V, A>
30where
31 V: View<A>,
32 A: Aggregate,
33{
34 async fn load(&self, view_id: &str) -> Result<Option<V>, PersistenceError> {
35 let result = load_view(&self.client, &self.view_name, view_id).await?;
36 let document = match result {
37 Some(item) => item,
38 None => return Ok(None),
39 };
40
41 let payload = bson::from_bson(document.get("payload").unwrap().clone()).unwrap();
42 let view: V = serde_json::from_value(payload)?;
43 Ok(Some(view))
44 }
45
46 async fn load_with_context(
47 &self,
48 view_id: &str,
49 ) -> Result<Option<(V, ViewContext)>, PersistenceError> {
50 let result = load_view(&self.client, &self.view_name, view_id).await?;
51 let document = match result {
52 Some(item) => item,
53 None => return Ok(None),
54 };
55
56 let version = document.get_i64("version").unwrap_or(0);
57 let payload = bson::from_bson(document.get("payload").unwrap().clone()).unwrap();
58 let view: V = serde_json::from_value(payload)?;
59 let context = ViewContext::new(view_id.to_string(), version);
60 Ok(Some((view, context)))
61 }
62
63 async fn update_view(&self, view: V, context: ViewContext) -> Result<(), PersistenceError> {
64 let collection = self
65 .client
66 .default_database()
67 .expect("Default database not configured")
68 .collection::<Document>(&self.view_name);
69
70 let view_id = context.view_instance_id;
71
72 let filter = doc! { "view_id": &view_id };
73 let update = doc! {
74 "$set": {
75 "payload": bson::to_bson(&view).unwrap(),
76 "version": context.version + 1,
77 }
78 };
79
80 let res = collection
81 .update_one(filter, update)
82 .upsert(true)
83 .await
84 .expect("Failed to update view");
85
86 println!(
87 "Modified {} documents in `{}` collection",
88 res.modified_count, &self.view_name
89 );
90
91 Ok(())
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use cqrs_es::doc::{Customer, CustomerEvent};
98 use cqrs_es::persist::{ViewContext, ViewRepository};
99
100 use crate::utils::tests::{mongodb_client, CustomerView};
101 use crate::MongoViewRepository;
102
103 #[tokio::test]
104 async fn test_view_repository() {
105 let repository =
106 MongoViewRepository::<CustomerView, Customer>::new("test_view", mongodb_client().await);
107
108 let test_view_id = uuid::Uuid::new_v4().to_string();
109
110 let view = CustomerView {
111 events: vec![CustomerEvent::NameAdded {
112 name: "Ferris".to_string(),
113 }],
114 };
115
116 repository
117 .update_view(view.clone(), ViewContext::new(test_view_id.to_string(), 0))
118 .await
119 .unwrap();
120
121 let found = repository.load(&test_view_id).await.unwrap().unwrap();
122
123 assert_eq!(found, view);
124 }
125}