fmodel_rust/saga.rs
1use crate::{ReactFunction, Saga3, Saga4, Saga5, Saga6, Sum, Sum3, Sum4, Sum5, Sum6};
2
3/// [Saga] is a datatype that represents the central point of control, deciding what to execute next (`A`), based on the action result (`AR`).
4/// It has two generic parameters `AR`/Action Result, `A`/Action , representing the type of the values that Saga may contain or use.
5/// `'a` is used as a lifetime parameter, indicating that all references contained within the struct (e.g., references within the function closures) must have a lifetime that is at least as long as 'a.
6///
7/// It is common to consider Event as Action Result, and Command as Action, but it is not mandatory.
8/// For example, Action Result can be a request response from a remote service.
9///
10/// ## Example
11///
12/// ```
13/// use fmodel_rust::saga::Saga;
14///
15/// fn saga<'a>() -> Saga<'a, OrderEvent, ShipmentCommand> {
16/// Saga {
17/// react: Box::new(|event| match event {
18/// OrderEvent::Created(created_event) => {
19/// vec![ShipmentCommand::Create(CreateShipmentCommand {
20/// shipment_id: created_event.order_id,
21/// order_id: created_event.order_id,
22/// customer_name: created_event.customer_name.to_owned(),
23/// items: created_event.items.to_owned(),
24/// })]
25/// }
26/// OrderEvent::Updated(_updated_event) => {
27/// vec![]
28/// }
29/// OrderEvent::Cancelled(_cancelled_event) => {
30/// vec![]
31/// }
32/// }),
33/// }
34/// }
35///
36/// #[derive(Debug, PartialEq)]
37/// #[allow(dead_code)]
38/// pub enum ShipmentCommand {
39/// Create(CreateShipmentCommand),
40/// }
41///
42/// #[derive(Debug, PartialEq)]
43/// pub struct CreateShipmentCommand {
44/// pub shipment_id: u32,
45/// pub order_id: u32,
46/// pub customer_name: String,
47/// pub items: Vec<String>,
48/// }
49///
50/// #[derive(Debug)]
51/// pub enum OrderEvent {
52/// Created(OrderCreatedEvent),
53/// Updated(OrderUpdatedEvent),
54/// Cancelled(OrderCancelledEvent),
55/// }
56///
57/// #[derive(Debug)]
58/// pub struct OrderCreatedEvent {
59/// pub order_id: u32,
60/// pub customer_name: String,
61/// pub items: Vec<String>,
62/// }
63///
64/// #[derive(Debug)]
65/// pub struct OrderUpdatedEvent {
66/// pub order_id: u32,
67/// pub updated_items: Vec<String>,
68/// }
69///
70/// #[derive(Debug)]
71/// pub struct OrderCancelledEvent {
72/// pub order_id: u32,
73/// }
74///
75/// let saga: Saga<OrderEvent, ShipmentCommand> = saga();
76/// let order_created_event = OrderEvent::Created(OrderCreatedEvent {
77/// order_id: 1,
78/// customer_name: "John Doe".to_string(),
79/// items: vec!["Item 1".to_string(), "Item 2".to_string()],
80/// });
81///
82/// let commands = (saga.react)(&order_created_event);
83/// ```
84pub struct Saga<'a, AR: 'a, A: 'a> {
85 /// The `react` function is driving the next action based on the action result.
86 pub react: ReactFunction<'a, AR, A>,
87}
88
89impl<'a, AR, A> Saga<'a, AR, A> {
90 /// Maps the Saga over the A/Action type parameter.
91 /// Creates a new instance of [Saga]`<AR, A2>`.
92 pub fn map_action<A2, F>(self, f: F) -> Saga<'a, AR, A2>
93 where
94 F: Fn(&A) -> A2 + Send + Sync + 'a,
95 {
96 let new_react = Box::new(move |ar: &AR| {
97 let a = (self.react)(ar);
98 a.into_iter().map(|a: A| f(&a)).collect()
99 });
100
101 Saga { react: new_react }
102 }
103
104 /// Maps the Saga over the AR/ActionResult type parameter.
105 /// Creates a new instance of [Saga]`<AR2, A>`.
106 pub fn map_action_result<AR2, F>(self, f: F) -> Saga<'a, AR2, A>
107 where
108 F: Fn(&AR2) -> AR + Send + Sync + 'a,
109 {
110 let new_react = Box::new(move |ar2: &AR2| {
111 let ar = f(ar2);
112 (self.react)(&ar)
113 });
114
115 Saga { react: new_react }
116 }
117
118 /// Combines two sagas into one.
119 /// Creates a new instance of a Saga by combining two sagas of type `AR`, `A` and `AR2`, `A2` into a new saga of type `Sum<AR, AR2>`, `Sum<A2, A>`
120 #[deprecated(
121 since = "0.8.0",
122 note = "Use the `merge` function instead. This ensures all your sagas can subscribe to all `Event`/`E` in the system."
123 )]
124 pub fn combine<AR2, A2>(self, saga2: Saga<'a, AR2, A2>) -> Saga<'a, Sum<AR, AR2>, Sum<A2, A>> {
125 let new_react = Box::new(move |ar: &Sum<AR, AR2>| match ar {
126 Sum::First(ar) => {
127 let a = (self.react)(ar);
128 a.into_iter().map(|a: A| Sum::Second(a)).collect()
129 }
130 Sum::Second(ar2) => {
131 let a2 = (saga2.react)(ar2);
132 a2.into_iter().map(|a: A2| Sum::First(a)).collect()
133 }
134 });
135
136 Saga { react: new_react }
137 }
138
139 /// Merges two sagas into one.
140 /// Creates a new instance of a Saga by merging two sagas of type `AR`, `A` and `AR`, `A2` into a new saga of type `AR`, `Sum<A, A2>`
141 /// Similar to `combine`, but the event type is the same for both sagas.
142 /// This ensures all your sagas can subscribe to all `Event`/`E` in the system.
143 pub fn merge<A2>(self, saga2: Saga<'a, AR, A2>) -> Saga<'a, AR, Sum<A2, A>> {
144 let new_react = Box::new(move |ar: &AR| {
145 let a: Vec<Sum<A2, A>> = (self.react)(ar)
146 .into_iter()
147 .map(|a: A| Sum::Second(a))
148 .collect();
149 let a2: Vec<Sum<A2, A>> = (saga2.react)(ar)
150 .into_iter()
151 .map(|a2: A2| Sum::First(a2))
152 .collect();
153
154 a.into_iter().chain(a2).collect()
155 });
156
157 Saga { react: new_react }
158 }
159
160 /// Merges three sagas into one.
161 pub fn merge3<A2, A3>(
162 self,
163 saga2: Saga<'a, AR, A2>,
164 saga3: Saga<'a, AR, A3>,
165 ) -> Saga3<'a, AR, A, A2, A3>
166 where
167 A: Clone,
168 A2: Clone,
169 A3: Clone,
170 {
171 self.merge(saga2)
172 .merge(saga3)
173 .map_action(|a: &Sum<A3, Sum<A2, A>>| match a {
174 Sum::First(a) => Sum3::Third(a.clone()),
175 Sum::Second(Sum::First(a)) => Sum3::Second(a.clone()),
176 Sum::Second(Sum::Second(a)) => Sum3::First(a.clone()),
177 })
178 }
179
180 /// Merges four sagas into one.
181 pub fn merge4<A2, A3, A4>(
182 self,
183 saga2: Saga<'a, AR, A2>,
184 saga3: Saga<'a, AR, A3>,
185 saga4: Saga<'a, AR, A4>,
186 ) -> Saga4<'a, AR, A, A2, A3, A4>
187 where
188 A: Clone,
189 A2: Clone,
190 A3: Clone,
191 A4: Clone,
192 {
193 self.merge(saga2).merge(saga3).merge(saga4).map_action(
194 |a: &Sum<A4, Sum<A3, Sum<A2, A>>>| match a {
195 Sum::First(a) => Sum4::Fourth(a.clone()),
196 Sum::Second(Sum::First(a)) => Sum4::Third(a.clone()),
197 Sum::Second(Sum::Second(Sum::First(a))) => Sum4::Second(a.clone()),
198 Sum::Second(Sum::Second(Sum::Second(a))) => Sum4::First(a.clone()),
199 },
200 )
201 }
202
203 #[allow(clippy::type_complexity)]
204 /// Merges five sagas into one.
205 pub fn merge5<A2, A3, A4, A5>(
206 self,
207 saga2: Saga<'a, AR, A2>,
208 saga3: Saga<'a, AR, A3>,
209 saga4: Saga<'a, AR, A4>,
210 saga5: Saga<'a, AR, A5>,
211 ) -> Saga5<'a, AR, A, A2, A3, A4, A5>
212 where
213 A: Clone,
214 A2: Clone,
215 A3: Clone,
216 A4: Clone,
217 A5: Clone,
218 {
219 self.merge(saga2)
220 .merge(saga3)
221 .merge(saga4)
222 .merge(saga5)
223 .map_action(|a: &Sum<A5, Sum<A4, Sum<A3, Sum<A2, A>>>>| match a {
224 Sum::First(a) => Sum5::Fifth(a.clone()),
225 Sum::Second(Sum::First(a)) => Sum5::Fourth(a.clone()),
226 Sum::Second(Sum::Second(Sum::First(a))) => Sum5::Third(a.clone()),
227 Sum::Second(Sum::Second(Sum::Second(Sum::First(a)))) => Sum5::Second(a.clone()),
228 Sum::Second(Sum::Second(Sum::Second(Sum::Second(a)))) => Sum5::First(a.clone()),
229 })
230 }
231
232 #[allow(clippy::type_complexity)]
233 /// Merges six sagas into one.
234 pub fn merge6<A2, A3, A4, A5, A6>(
235 self,
236 saga2: Saga<'a, AR, A2>,
237 saga3: Saga<'a, AR, A3>,
238 saga4: Saga<'a, AR, A4>,
239 saga5: Saga<'a, AR, A5>,
240 saga6: Saga<'a, AR, A6>,
241 ) -> Saga6<'a, AR, A, A2, A3, A4, A5, A6>
242 where
243 A: Clone,
244 A2: Clone,
245 A3: Clone,
246 A4: Clone,
247 A5: Clone,
248 A6: Clone,
249 {
250 self.merge(saga2)
251 .merge(saga3)
252 .merge(saga4)
253 .merge(saga5)
254 .merge(saga6)
255 .map_action(
256 |a: &Sum<A6, Sum<A5, Sum<A4, Sum<A3, Sum<A2, A>>>>>| match a {
257 Sum::First(a) => Sum6::Sixth(a.clone()),
258 Sum::Second(Sum::First(a)) => Sum6::Fifth(a.clone()),
259 Sum::Second(Sum::Second(Sum::First(a))) => Sum6::Fourth(a.clone()),
260 Sum::Second(Sum::Second(Sum::Second(Sum::First(a)))) => Sum6::Third(a.clone()),
261 Sum::Second(Sum::Second(Sum::Second(Sum::Second(Sum::First(a))))) => {
262 Sum6::Second(a.clone())
263 }
264 Sum::Second(Sum::Second(Sum::Second(Sum::Second(Sum::Second(a))))) => {
265 Sum6::First(a.clone())
266 }
267 },
268 )
269 }
270}
271
272/// Formalizes the `Action Computation` algorithm for the `saga` to handle events/action_results, and produce new commands/actions.
273pub trait ActionComputation<AR, A> {
274 /// Computes new commands/actions based on the event/action_result.
275 fn compute_new_actions(&self, event: &AR) -> Vec<A>;
276}
277
278impl<AR, A> ActionComputation<AR, A> for Saga<'_, AR, A> {
279 /// Computes new commands/actions based on the event/action_result.
280 fn compute_new_actions(&self, event: &AR) -> Vec<A> {
281 (self.react)(event).into_iter().collect()
282 }
283}