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
13#[cfg(not(feature = "not-send-futures"))]
14pub trait ViewStateRepository<E, S, Error> {
15    /// Fetches current state, based on the event.
16    /// Desugared `async fn fetch_state(&self, event: &E) -> Result<Option<S>, Error>;` to a normal `fn` that returns `impl Future`, and adds bound `Send`.
17    /// 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.
18    fn fetch_state(&self, event: &E) -> impl Future<Output = Result<Option<S>, Error>> + Send;
19    /// Saves the new state.
20    /// Desugared `async fn save(&self, state: &S) -> Result<S, Error>;` to a normal `fn` that returns `impl Future`, and adds bound `Send`.
21    /// 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.
22    fn save(&self, state: &S) -> impl Future<Output = Result<S, Error>> + Send;
23}
24
25/// View State Repository trait
26///
27/// Generic parameters:
28///
29/// - `E` - Event
30/// - `S` - State
31/// - `Error` - Error
32#[cfg(feature = "not-send-futures")]
33pub trait ViewStateRepository<E, S, Error> {
34    /// Fetches current state, based on the event.
35    /// Desugared `async fn fetch_state(&self, event: &E) -> Result<Option<S>, Error>;` to a normal `fn` that returns `impl Future`.
36    /// You can freely move between the `async fn` and `-> impl Future` spelling in your traits and impls.
37    fn fetch_state(&self, event: &E) -> impl Future<Output = Result<Option<S>, Error>>;
38    /// Saves the new state.
39    /// Desugared `async fn save(&self, state: &S) -> Result<S, Error>;` to a normal `fn` that returns `impl Future`.
40    /// You can freely move between the `async fn` and `-> impl Future` spelling in your traits and impls.
41    fn save(&self, state: &S) -> impl Future<Output = Result<S, Error>>;
42}
43
44/// Materialized View.
45///
46/// It is using a `View` / [ViewStateComputation] to compute new state based on the current state and the event.
47/// It is using a [ViewStateRepository] to fetch the current state and to save the new state.
48///
49/// Generic parameters:
50///
51/// - `S` - State
52/// - `E` - Event
53/// - `Repository` - View State repository
54/// - `View` - View
55/// - `Error` - Error
56pub struct MaterializedView<S, E, Repository, View, Error>
57where
58    Repository: ViewStateRepository<E, S, Error>,
59    View: ViewStateComputation<E, S>,
60{
61    repository: Repository,
62    view: View,
63    _marker: PhantomData<(S, E, Error)>,
64}
65
66impl<S, E, Repository, View, Error> ViewStateComputation<E, S>
67    for MaterializedView<S, E, Repository, View, Error>
68where
69    Repository: ViewStateRepository<E, S, Error>,
70    View: ViewStateComputation<E, S>,
71{
72    /// Computes new state based on the current state and the events.
73    fn compute_new_state(&self, current_state: Option<S>, events: &[&E]) -> S {
74        self.view.compute_new_state(current_state, events)
75    }
76}
77
78#[cfg(not(feature = "not-send-futures"))]
79impl<S, E, Repository, View, Error> ViewStateRepository<E, S, Error>
80    for MaterializedView<S, E, Repository, View, Error>
81where
82    Repository: ViewStateRepository<E, S, Error> + Sync,
83    View: ViewStateComputation<E, S> + Sync,
84    E: Sync,
85    S: Sync,
86    Error: Sync,
87{
88    /// Fetches current state, based on the event.
89    async fn fetch_state(&self, event: &E) -> Result<Option<S>, Error> {
90        let state = self.repository.fetch_state(event).await?;
91        Ok(state)
92    }
93    /// Saves the new state.
94    async fn save(&self, state: &S) -> Result<S, Error> {
95        self.repository.save(state).await
96    }
97}
98
99#[cfg(feature = "not-send-futures")]
100impl<S, E, Repository, View, Error> ViewStateRepository<E, S, Error>
101    for MaterializedView<S, E, Repository, View, Error>
102where
103    Repository: ViewStateRepository<E, S, Error>,
104    View: ViewStateComputation<E, S>,
105{
106    /// Fetches current state, based on the event.
107    async fn fetch_state(&self, event: &E) -> Result<Option<S>, Error> {
108        let state = self.repository.fetch_state(event).await?;
109        Ok(state)
110    }
111    /// Saves the new state.
112    async fn save(&self, state: &S) -> Result<S, Error> {
113        self.repository.save(state).await
114    }
115}
116
117#[cfg(not(feature = "not-send-futures"))]
118impl<S, E, Repository, View, Error> MaterializedView<S, E, Repository, View, Error>
119where
120    Repository: ViewStateRepository<E, S, Error> + Sync,
121    View: ViewStateComputation<E, S> + Sync,
122    E: Sync,
123    S: Sync,
124    Error: Sync,
125{
126    /// Creates a new instance of [MaterializedView].
127    pub fn new(repository: Repository, view: View) -> Self {
128        MaterializedView {
129            repository,
130            view,
131            _marker: PhantomData,
132        }
133    }
134    /// 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.
135    pub async fn handle(&self, event: &E) -> Result<S, Error> {
136        let state = self.fetch_state(event).await?;
137        let new_state = self.compute_new_state(state, &[event]);
138        let saved_state = self.save(&new_state).await?;
139        Ok(saved_state)
140    }
141}
142
143#[cfg(feature = "not-send-futures")]
144impl<S, E, Repository, View, Error> MaterializedView<S, E, Repository, View, Error>
145where
146    Repository: ViewStateRepository<E, S, Error>,
147    View: ViewStateComputation<E, S>,
148{
149    /// Creates a new instance of [MaterializedView].
150    pub fn new(repository: Repository, view: View) -> Self {
151        MaterializedView {
152            repository,
153            view,
154            _marker: PhantomData,
155        }
156    }
157    /// 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.
158    pub async fn handle(&self, event: &E) -> Result<S, Error> {
159        let state = self.fetch_state(event).await?;
160        let new_state = self.compute_new_state(state, &[event]);
161        let saved_state = self.save(&new_state).await?;
162        Ok(saved_state)
163    }
164}