Skip to main content

horfimbor_eventsource/
repository.rs

1//! the repository mod is where the heavy lifting occurs
2//! read / write to the db, play event, add command ...
3
4use std::cmp::Ordering;
5use std::fmt::Debug;
6use std::marker::PhantomData;
7
8use async_trait::async_trait;
9use kurrentdb::{
10    AppendToStreamOptions, Client as EventDb, Error, EventData, ReadStreamOptions, StreamPosition,
11    StreamState, SubscribeToPersistentSubscriptionOptions,
12};
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Serialize};
15
16use crate::cache_db::CacheDb;
17use crate::helper::create_subscription;
18use crate::metadata::{CompleteEvent, Metadata};
19use crate::model_key::ModelKey;
20use crate::{Dto, EventSourceError, EventSourceStateError};
21use crate::{State, Stream};
22
23/// the `DtoRepository` is the reading part of the event storage
24/// multiple `DtoRepository` can listen to the event stream but produce
25/// different model.
26#[derive(Clone)]
27#[allow(clippy::module_name_repetitions)]
28pub struct DtoRepository<D, C>
29where
30    D: Dto,
31    C: CacheDb<D>,
32{
33    event_db: EventDb,
34    cache_db: C,
35    repository_kind: RepositoryKind,
36    dto: PhantomData<D>,
37}
38
39/// the `StateRepository` is the central piece of the project
40/// the update are done with Command on State
41/// and the query are done by recomputing all event or reading the `CacheDB`
42#[derive(Clone)]
43#[allow(clippy::module_name_repetitions)]
44pub struct StateRepository<S, C>
45where
46    S: State,
47    C: CacheDb<S>,
48{
49    event_db: EventDb,
50    state_db: C,
51    repository_kind: RepositoryKind,
52    state: PhantomData<S>,
53}
54
55/// `ModelWithPosition` is a helper to add the position to any model
56#[derive(Default, Serialize, Deserialize, Debug, Clone)]
57pub struct ModelWithPosition<M> {
58    position: Option<u64>,
59    model: M,
60}
61
62impl<M> ModelWithPosition<M>
63where
64    M: Dto,
65{
66    /// get the current state
67    pub const fn state(&self) -> &M {
68        &self.model
69    }
70
71    /// play one event to the current model to compute the next
72    pub fn play_event(&mut self, event: &M::Event, position: Option<u64>) {
73        self.model.play_event(event);
74
75        self.position = position;
76    }
77
78    /// the position is the number of event in the event store
79    /// `None` indicate that the topic is empty
80    pub const fn position(&self) -> Option<u64> {
81        self.position
82    }
83}
84
85/// the enum `RepositoryKind` allow to differentiate the state and dto caches
86/// and also to have multiple dto listening to the same events
87#[derive(Clone)]
88pub enum RepositoryKind {
89    /// this repository kind have a cache that is not prefixes
90    State,
91    /// this is most likely be the name of the dto
92    Dto(&'static str),
93}
94
95impl RepositoryKind {
96    const fn to_cache_prefix(&self) -> Option<&'static str> {
97        match self {
98            Self::State => None,
99            Self::Dto(p) => Some(p),
100        }
101    }
102}
103
104#[allow(missing_docs)]
105#[async_trait]
106/// the trait `Repository` allow to reconstruct the `Dto` from the `EventDb`
107pub trait DtoRepositoryConstructor<D, C>: Clone + Send
108where
109    D: Dto,
110    C: CacheDb<D>,
111{
112    /// A repository need the `EventDb`, a cache system and a kind,
113    /// the cache system can be the `NoCache` provided in `cache_db`,
114    /// the kind is used only to avoid cache collision,
115    /// the type system take care of everything else.
116    fn new(event_db: EventDb, cache_db: C, repository_kind: RepositoryKind) -> Self;
117}
118
119#[allow(missing_docs)]
120#[async_trait]
121/// the trait `Repository` allow to reconstruct the `Dto` from the `EventDb`
122pub trait StateRepositoryConstructor<D, C>: Clone + Send
123where
124    D: Dto,
125    C: CacheDb<D>,
126{
127    /// A repository need the `EventDb`, a cache system and a kind,
128    /// the cache system can be the `NoCache` provided in `cache_db`,
129    /// the type system take care of everything else.
130    fn new(event_db: EventDb, cache_db: C) -> Self;
131}
132
133#[allow(missing_docs)]
134#[async_trait]
135/// the trait `Repository` allow to reconstruct the `Dto` from the `EventDb`
136pub trait Repository<D, C>: Clone + Send
137where
138    D: Dto,
139    C: CacheDb<D>,
140{
141    /// Getter for the `EventDb`
142    fn event_db(&self) -> &EventDb;
143
144    /// Getter for the cache
145    fn cache_db(&self) -> &C;
146
147    /// Getter for the cache
148    fn repository_kind(&self) -> &RepositoryKind;
149
150    async fn get_model(&self, key: &ModelKey) -> Result<ModelWithPosition<D>, EventSourceError>
151    where
152        D: Dto + DeserializeOwned,
153    {
154        let value = self
155            .cache_db()
156            .get(self.repository_kind().to_cache_prefix(), key)
157            .map_err(EventSourceError::CacheDbError)?;
158
159        self.complete_from_es(key, &value).await
160    }
161
162    async fn complete_from_es(
163        &self,
164        key: &ModelKey,
165        value: &ModelWithPosition<D>,
166    ) -> Result<ModelWithPosition<D>, EventSourceError> {
167        let mut dto: D = value.model.clone();
168        let mut position = value.position;
169
170        let options = ReadStreamOptions::default();
171        let options = if let Some(position) = value.position {
172            options.position(StreamPosition::Position(position + 1))
173        } else {
174            options.position(StreamPosition::Start)
175        };
176
177        let mut stream = self
178            .event_db()
179            .read_stream(key.format(), &options)
180            .await
181            .map_err(EventSourceError::EventStore)?;
182
183        while let Ok(Some(json_event)) = stream.next().await {
184            let original_event = json_event.get_original_event();
185
186            let metadata: Metadata =
187                serde_json::from_slice(original_event.custom_metadata.as_ref())
188                    .map_err(EventSourceError::Serde)?;
189
190            if metadata.is_event() {
191                let event = original_event
192                    .as_json::<D::Event>()
193                    .map_err(EventSourceError::Serde)?;
194
195                dto.play_event(&event);
196            }
197
198            position = Some(original_event.revision);
199        }
200
201        let result = ModelWithPosition {
202            position,
203            model: dto,
204        };
205
206        Ok(result)
207    }
208
209    async fn cache_dto(&self, stream: &Stream, group_name: &str) -> Result<(), EventSourceError> {
210        create_subscription(self.event_db(), stream, group_name)
211            .await
212            .map_err(EventSourceError::EventStore)?;
213
214        let options = SubscribeToPersistentSubscriptionOptions::default().buffer_size(1);
215
216        let mut sub = self
217            .event_db()
218            .subscribe_to_persistent_subscription(stream.to_string(), group_name, &options)
219            .await
220            .map_err(EventSourceError::EventStore)?;
221
222        loop {
223            let rcv_event = sub.next().await.map_err(EventSourceError::EventStore)?;
224
225            let Some(event) = rcv_event.event.as_ref() else {
226                continue;
227            };
228
229            let model_key: ModelKey = event
230                .stream_id()
231                .try_into()
232                .map_err(EventSourceError::ModelKey)?;
233
234            let mut model = self
235                .cache_db()
236                .get(self.repository_kind().to_cache_prefix(), &model_key)
237                .map_err(EventSourceError::CacheDbError)?;
238
239            let ordering = if event.revision == 0 {
240                if model.position.is_some() {
241                    Ordering::Greater
242                } else {
243                    Ordering::Equal
244                }
245            } else {
246                model
247                    .position
248                    .map_or(Ordering::Less, |pos| pos.cmp(&(event.revision)))
249            };
250
251            match ordering {
252                Ordering::Less => {
253                    model = self.complete_from_es(&model_key, &model).await?;
254
255                    self.cache_db()
256                        .set(&model_key, model, self.repository_kind().to_cache_prefix())
257                        .map_err(EventSourceError::CacheDbError)?;
258                }
259                Ordering::Equal | Ordering::Greater => {}
260            }
261
262            sub.ack(&rcv_event)
263                .await
264                .map_err(EventSourceError::EventStore)?;
265        }
266    }
267
268    /// # Errors
269    ///
270    /// Will return `Err` if input is not in the format `index@stream_id`
271    fn split_event_id(str: &str) -> Result<(&str, &str), EventSourceError> {
272        let mut iter = str.split('@');
273
274        if let (Some(index), Some(stream_id)) = (iter.next(), iter.next()) {
275            return Ok((index, stream_id));
276        }
277
278        Err(EventSourceError::Position(format!(
279            "{str} isnt in the format index@stream_id"
280        )))
281    }
282}
283
284impl<D, C> DtoRepositoryConstructor<D, C> for DtoRepository<D, C>
285where
286    D: Dto,
287    C: CacheDb<D>,
288{
289    fn new(event_db: EventDb, cache_db: C, repository_kind: RepositoryKind) -> Self {
290        Self {
291            event_db,
292            cache_db,
293            repository_kind,
294            dto: PhantomData,
295        }
296    }
297}
298
299impl<D, C> Repository<D, C> for DtoRepository<D, C>
300where
301    D: Dto,
302    C: CacheDb<D>,
303{
304    fn event_db(&self) -> &EventDb {
305        &self.event_db
306    }
307    fn cache_db(&self) -> &C {
308        &self.cache_db
309    }
310
311    fn repository_kind(&self) -> &RepositoryKind {
312        &self.repository_kind
313    }
314}
315
316impl<S, C> StateRepositoryConstructor<S, C> for StateRepository<S, C>
317where
318    S: State,
319    C: CacheDb<S>,
320{
321    fn new(event_db: EventDb, state_db: C) -> Self {
322        Self {
323            event_db,
324            state_db,
325            repository_kind: RepositoryKind::State,
326            state: PhantomData,
327        }
328    }
329}
330
331impl<S, C> Repository<S, C> for StateRepository<S, C>
332where
333    S: State,
334    C: CacheDb<S>,
335{
336    fn event_db(&self) -> &EventDb {
337        &self.event_db
338    }
339    fn cache_db(&self) -> &C {
340        &self.state_db
341    }
342
343    fn repository_kind(&self) -> &RepositoryKind {
344        &self.repository_kind
345    }
346}
347
348/// Appending event can resolve with multiple correct behavior
349#[derive(Eq, PartialEq)]
350pub enum AddedEvent {
351    /// Successfully added, nothing more to do
352    Success,
353
354    /// The event wasn't added, the command need to be retried
355    NeedRetry,
356}
357
358impl<S, C> StateRepository<S, C>
359where
360    S: State,
361    C: CacheDb<S>,
362{
363    /// # Errors
364    ///
365    /// Will return `Err` if events cannot be added to the eventstore
366    pub async fn add_command(
367        &self,
368        key: &ModelKey,
369        command: S::Command,
370        previous_metadata: Option<&Metadata>,
371    ) -> Result<S, EventSourceStateError>
372    where
373        S: State,
374    {
375        let mut model: S;
376        let events: Vec<S::Event>;
377
378        loop {
379            let (l_model, l_events, retry) = self
380                .try_append(key, command.clone(), previous_metadata)
381                .await?;
382            if retry == AddedEvent::NeedRetry {
383                continue;
384            }
385
386            model = l_model;
387            events = l_events;
388
389            break;
390        }
391
392        for event in &events {
393            model.play_event(event);
394        }
395
396        Ok(model)
397    }
398
399    async fn try_append(
400        &self,
401        key: &ModelKey,
402        command: S::Command,
403        previous_metadata: Option<&Metadata>,
404    ) -> Result<(S, Vec<S::Event>, AddedEvent), EventSourceStateError>
405    where
406        S: State + Sync,
407    {
408        let model: ModelWithPosition<S> = self
409            .get_model(key)
410            .await
411            .map_err(EventSourceStateError::EventSourceError)?;
412
413        let state = model.model;
414
415        let events = state
416            .try_command(command.clone())
417            .map_err(|e| EventSourceStateError::State(format!("{e}")))?;
418
419        let options = model.position.map_or_else(
420            || AppendToStreamOptions::default().stream_state(StreamState::NoStream),
421            |position| {
422                AppendToStreamOptions::default().stream_state(StreamState::StreamRevision(position))
423            },
424        );
425
426        let command_metadata = CompleteEvent::from_command(&command, previous_metadata)
427            .map_err(|e| EventSourceStateError::EventSourceError(EventSourceError::Serde(e)))?;
428
429        let mut events_data = vec![command_metadata.clone()];
430
431        let mut previous_metadata = command_metadata.metadata().to_owned();
432
433        let res_events = events.clone();
434
435        for event in events {
436            let event_metadata = CompleteEvent::from_event(&event, &previous_metadata)
437                .map_err(|e| EventSourceStateError::EventSourceError(EventSourceError::Serde(e)))?;
438
439            events_data.push(event_metadata.clone());
440            event_metadata.metadata().clone_into(&mut previous_metadata);
441        }
442
443        let retry = self
444            .try_append_event_data(key, &options, events_data)
445            .await?;
446
447        Ok((state, res_events, retry))
448    }
449
450    async fn try_append_event_data(
451        &self,
452        key: &ModelKey,
453        options: &AppendToStreamOptions,
454        events_with_data: Vec<CompleteEvent>,
455    ) -> Result<AddedEvent, EventSourceStateError>
456    where
457        S: State,
458    {
459        let mut err = Ok(());
460        let events: Vec<EventData> = events_with_data
461            .into_iter()
462            .filter_map(|e| match e.full_event_data() {
463                Ok(event) => Some(event),
464                Err(e) => {
465                    err = Err(EventSourceError::Serde(e));
466                    None
467                }
468            })
469            .collect();
470        err.map_err(EventSourceStateError::EventSourceError)?;
471
472        let appended = self
473            .event_db
474            .append_to_stream(key.format(), options, events)
475            .await;
476
477        match appended {
478            Ok(_) => Ok(AddedEvent::Success),
479            Err(Error::WrongExpectedVersion { .. }) => Ok(AddedEvent::NeedRetry),
480            Err(e) => Err(EventSourceStateError::EventSourceError(
481                EventSourceError::EventStore(e),
482            )),
483        }
484    }
485}