fmodel_rust/
saga_manager.rs

1use std::future::Future;
2use std::marker::PhantomData;
3
4use crate::saga::ActionComputation;
5
6/// Publishes the action/command to some external system.
7///
8/// Generic parameter:
9///
10/// - `A`. - action
11/// - `Error` - error
12pub trait ActionPublisher<A, Error> {
13    /// Publishes the action/command to some external system, returning either the actions that are successfully published or error.
14    /// Desugared `async fn publish(&self, action: &[A]) -> Result<Vec<A>, Error>;` to a normal `fn` that returns `impl Future`, and adds bound `Send`.
15    /// 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.
16    fn publish(&self, action: &[A]) -> impl Future<Output = Result<Vec<A>, Error>> + Send;
17}
18
19/// Saga Manager.
20///
21/// It is using a `Saga` to react to the action result and to publish the new actions.
22/// It is using an [ActionPublisher] to publish the new actions.
23///
24/// Generic parameters:
25/// - `A` - Action / Command
26/// - `AR` - Action Result / Event
27/// - `Publisher` - Action Publisher
28/// - `Error` - Error
29pub struct SagaManager<A, AR, Publisher, Saga, Error>
30where
31    Publisher: ActionPublisher<A, Error>,
32    Saga: ActionComputation<AR, A>,
33{
34    action_publisher: Publisher,
35    saga: Saga,
36    _marker: PhantomData<(A, AR, Error)>,
37}
38
39impl<A, AR, Publisher, Saga, Error> ActionComputation<AR, A>
40    for SagaManager<A, AR, Publisher, Saga, Error>
41where
42    Publisher: ActionPublisher<A, Error>,
43    Saga: ActionComputation<AR, A>,
44{
45    /// Computes new actions based on the action result.
46    fn compute_new_actions(&self, action_result: &AR) -> Vec<A> {
47        self.saga.compute_new_actions(action_result)
48    }
49}
50
51impl<A, AR, Publisher, Saga, Error> ActionPublisher<A, Error>
52    for SagaManager<A, AR, Publisher, Saga, Error>
53where
54    Publisher: ActionPublisher<A, Error> + Sync,
55    Saga: ActionComputation<AR, A> + Sync,
56    A: Sync,
57    AR: Sync,
58    Error: Sync,
59{
60    /// Publishes the action/command to some external system, returning either the actions that are successfully published or error.
61    async fn publish(&self, action: &[A]) -> Result<Vec<A>, Error> {
62        self.action_publisher.publish(action).await
63    }
64}
65
66impl<A, AR, Publisher, Saga, Error> SagaManager<A, AR, Publisher, Saga, Error>
67where
68    Publisher: ActionPublisher<A, Error> + Sync,
69    Saga: ActionComputation<AR, A> + Sync,
70    A: Sync,
71    AR: Sync,
72    Error: Sync,
73{
74    /// Creates a new instance of [SagaManager].
75    pub fn new(action_publisher: Publisher, saga: Saga) -> Self {
76        SagaManager {
77            action_publisher,
78            saga,
79            _marker: PhantomData,
80        }
81    }
82    /// Handles the `action result` by computing new `actions` based on `action result`, and publishing new `actions` to the external system.
83    /// In most cases:
84    ///  - the `action result` is an `event` that you react,
85    ///  - the `actions` are `commands` that you publish downstream.
86    pub async fn handle(&self, action_result: &AR) -> Result<Vec<A>, Error> {
87        let new_actions = self.compute_new_actions(action_result);
88        let published_actions = self.publish(&new_actions).await?;
89        Ok(published_actions)
90    }
91}