fmodel_rust/
materialized_view.rs

1use std::future::Future;
2use std::marker::PhantomData;
3
4use crate::view::ViewStateComputation;
5
6/// View State Repository trait
7///
8/// Generic parameters:
9///
10/// - `E` - Event
11/// - `S` - State
12/// - `Error` - Error
13pub trait ViewStateRepository<E, S, Error> {
14    /// Fetches current state, based on the event.
15    /// Desugared `async fn fetch_state(&self, event: &E) -> Result<Option<S>, Error>;` to a normal `fn` that returns `impl Future`, and adds bound `Send`.
16    /// You can freely move between the `async fn` and `-> impl Future` spelling in your traits and impls. This is true even when one form has a Send bound.
17    fn fetch_state(&self, event: &E) -> impl Future<Output = Result<Option<S>, Error>> + Send;
18    /// Saves the new state.
19    /// Desugared `async fn save(&self, state: &S) -> Result<S, Error>;` to a normal `fn` that returns `impl Future`, and adds bound `Send`.
20    /// You can freely move between the `async fn` and `-> impl Future` spelling in your traits and impls. This is true even when one form has a Send bound.
21    fn save(&self, state: &S) -> impl Future<Output = Result<S, Error>> + Send;
22}
23
24/// Materialized View.
25///
26/// It is using a `View` / [ViewStateComputation] to compute new state based on the current state and the event.
27/// It is using a [ViewStateRepository] to fetch the current state and to save the new state.
28///
29/// Generic parameters:
30///
31/// - `S` - State
32/// - `E` - Event
33/// - `Repository` - View State repository
34/// - `View` - View
35/// - `Error` - Error
36pub struct MaterializedView<S, E, Repository, View, Error>
37where
38    Repository: ViewStateRepository<E, S, Error>,
39    View: ViewStateComputation<E, S>,
40{
41    repository: Repository,
42    view: View,
43    _marker: PhantomData<(S, E, Error)>,
44}
45
46impl<S, E, Repository, View, Error> ViewStateComputation<E, S>
47    for MaterializedView<S, E, Repository, View, Error>
48where
49    Repository: ViewStateRepository<E, S, Error>,
50    View: ViewStateComputation<E, S>,
51{
52    /// Computes new state based on the current state and the events.
53    fn compute_new_state(&self, current_state: Option<S>, events: &[&E]) -> S {
54        self.view.compute_new_state(current_state, events)
55    }
56}
57
58impl<S, E, Repository, View, Error> ViewStateRepository<E, S, Error>
59    for MaterializedView<S, E, Repository, View, Error>
60where
61    Repository: ViewStateRepository<E, S, Error> + Sync,
62    View: ViewStateComputation<E, S> + Sync,
63    E: Sync,
64    S: Sync,
65    Error: Sync,
66{
67    /// Fetches current state, based on the event.
68    async fn fetch_state(&self, event: &E) -> Result<Option<S>, Error> {
69        let state = self.repository.fetch_state(event).await?;
70        Ok(state)
71    }
72    /// Saves the new state.
73    async fn save(&self, state: &S) -> Result<S, Error> {
74        self.repository.save(state).await
75    }
76}
77
78impl<S, E, Repository, View, Error> MaterializedView<S, E, Repository, View, Error>
79where
80    Repository: ViewStateRepository<E, S, Error> + Sync,
81    View: ViewStateComputation<E, S> + Sync,
82    E: Sync,
83    S: Sync,
84    Error: Sync,
85{
86    /// Creates a new instance of [MaterializedView].
87    pub fn new(repository: Repository, view: View) -> Self {
88        MaterializedView {
89            repository,
90            view,
91            _marker: PhantomData,
92        }
93    }
94    /// Handles the event by fetching the state from the repository, computing new state based on the current state and the event, and saving the new state to the repository.
95    pub async fn handle(&self, event: &E) -> Result<S, Error> {
96        let state = self.fetch_state(event).await?;
97        let new_state = self.compute_new_state(state, &[event]);
98        let saved_state = self.save(&new_state).await?;
99        Ok(saved_state)
100    }
101}