fmodel_rust/
lib.rs

1#![deny(missing_docs)]
2//! # FModel Rust
3//!
4//! When you’re developing an information system to automate the activities of the business, you are modeling the business.
5//! The abstractions that you design, the behaviors that you implement, and the UI interactions that you build all reflect
6//! the business — together, they constitute the model of the domain.
7//!
8//! ![event-modeling](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/event-modeling.png)
9//!
10//! ## `IOR<Library, Inspiration>`
11//!
12//! This crate can be used as a library, or as an inspiration, or both. It provides just enough tactical Domain-Driven
13//! Design patterns, optimised for Event Sourcing and CQRS.
14//!
15//! ![onion architecture image](https://github.com/fraktalio/fmodel/blob/d8643a7d0de30b79f0b904f7a40233419c463fc8/.assets/onion.png?raw=true)
16//!
17//!## Decider
18//!
19//! `Decider` is a datatype/struct that represents the main decision-making algorithm. It belongs to the Domain layer. It
20//! has three
21//! generic parameters `C`, `S`, `E` , representing the type of the values that `Decider` may contain or use.
22//! `Decider` can be specialized for any type `C` or `S` or `E` because these types do not affect its
23//! behavior. `Decider` behaves the same for `C`=`Int` or `C`=`YourCustomType`, for example.
24//!
25//! `Decider` is a pure domain component.
26//!
27//! - `C` - Command
28//! - `S` - State
29//! - `E` - Event
30//!
31//! ```rust
32//! pub type DecideFunction<'a, C, S, E> = Box<dyn Fn(&C, &S) -> Vec<E> + 'a + Send + Sync>;
33//! pub type EvolveFunction<'a, S, E> = Box<dyn Fn(&S, &E) -> S + 'a + Send + Sync>;
34//! pub type InitialStateFunction<'a, S> = Box<dyn Fn() -> S + 'a + Send + Sync>;
35//!
36//! pub struct Decider<'a, C: 'a, S: 'a, E: 'a> {
37//!     pub decide: DecideFunction<'a, C, S, E>,
38//!     pub evolve: EvolveFunction<'a, S, E>,
39//!     pub initial_state: InitialStateFunction<'a, S>,
40//! }
41//! ```
42//!
43//! Additionally, `initialState` of the Decider is introduced to gain more control over the initial state of the Decider.
44//!
45//! ### Event-sourcing aggregate
46//!
47//! [aggregate::EventSourcedAggregate]  is using/delegating a `Decider` to handle commands and produce new events.
48//!
49//! It belongs to the Application layer.
50//!
51//! In order to handle the command, aggregate needs to fetch the current state (represented as a list/vector of events)
52//! via `EventRepository.fetchEvents` async function, and then delegate the command to the decider which can produce new
53//! events as
54//! a result. Produced events are then stored via `EventRepository.save` async function.
55//!
56//! It is a formalization of the event sourced information system.
57//!
58//! ### State-stored aggregate
59//!
60//! [aggregate::StateStoredAggregate] is using/delegating a `Decider` to handle commands and produce new state.
61//!
62//! It belongs to the Application layer.
63//!
64//! In order to handle the command, aggregate needs to fetch the current state via `StateRepository.fetchState` async function first,
65//! and then
66//! delegate the command to the decider which can produce new state as a result. New state is then stored
67//! via `StateRepository.save` async function.
68//!
69//! ## View
70//!
71//! `View`  is a datatype that represents the event handling algorithm, responsible for translating the events into
72//! denormalized state, which is more adequate for querying. It belongs to the Domain layer. It is usually used to create
73//! the view/query side of the CQRS pattern. Obviously, the command side of the CQRS is usually event-sourced aggregate.
74//!
75//! It has two generic parameters `S`, `E`, representing the type of the values that `View` may contain or use.
76//! `View` can be specialized for any type of `S`, `E` because these types do not affect its behavior.
77//! `View` behaves the same for `E`=`Int` or `E`=`YourCustomType`, for example.
78//!
79//! `View` is a pure domain component.
80//!
81//! - `S` - State
82//! - `E` - Event
83//!
84//! ```rust
85//! pub type EvolveFunction<'a, S, E> = Box<dyn Fn(&S, &E) -> S + 'a + Send + Sync>;
86//! pub type InitialStateFunction<'a, S> = Box<dyn Fn() -> S + 'a + Send + Sync>;
87//!
88//! pub struct View<'a, S: 'a, E: 'a> {
89//!     pub evolve: EvolveFunction<'a, S, E>,
90//!     pub initial_state: InitialStateFunction<'a, S>,
91//! }
92//! ```
93//!
94//! ### Materialized View
95//!
96//! [materialized_view::MaterializedView] is using/delegating a `View` to handle events of type `E` and to maintain
97//! a state of denormalized
98//! projection(s) as a
99//! result. Essentially, it represents the query/view side of the CQRS pattern.
100//!
101//! It belongs to the Application layer.
102//!
103//! In order to handle the event, materialized view needs to fetch the current state via `ViewStateRepository.fetchState`
104//! suspending function first, and then delegate the event to the view, which can produce new state as a result. New state
105//! is then stored via `ViewStateRepository.save` suspending function.
106//!
107//!
108//! ## Saga
109//!
110//! `Saga` is a datatype that represents the central point of control, deciding what to execute next (`A`), based on the action result (`AR`).
111//! It has two generic parameters `AR`/Action Result, `A`/Action , representing the type of the values that Saga may contain or use.
112//! `'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.
113//!
114//! `Saga` is a pure domain component.
115//!
116//! - `AR` - Action Result/Event
117//! - `A` - Action/Command
118//!
119//! ```rust
120//! pub type ReactFunction<'a, AR, A> = Box<dyn Fn(&AR) -> Vec<A> + 'a + Send + Sync>;
121//! pub struct Saga<'a, AR: 'a, A: 'a> {
122//!     pub react: ReactFunction<'a, AR, A>,
123//! }
124//! ```
125//!
126//! ### Saga Manager
127//!
128//! [saga_manager::SagaManager] is using/delegating a `Saga` to react to the action result and to publish the new actions.
129//!
130//! It belongs to the Application layer.
131//!
132//! It is using a [saga::Saga] to react to the action result and to publish the new actions.
133//! It is using an [saga_manager::ActionPublisher] to publish the new actions.
134//!
135//! ## Clear separation between data and behaviour
136//!
137//!```rust
138//! use fmodel_rust::decider::Decider;
139//! // ## Algebraic Data Types
140//! //
141//! // In Rust, we can use ADTs to model our application's domain entities and relationships in a functional way, clearly defining the set of possible values and states.
142//! // Rust has two main types of ADTs: `enum` and `struct`.
143//! //
144//! // - `enum` is used to define a type that can take on one of several possible variants - modeling a `sum/OR` type.
145//! // - `struct` is used to express a type that has named fields - modeling a `product/AND` type.
146//! //
147//! // ADTs will help with
148//! //
149//! // - representing the business domain in the code accurately
150//! // - enforcing correctness
151//! // - reducing the likelihood of bugs.
152//!
153//!
154//! // ### `C` / Command / Intent to change the state of the system
155//!
156//! // models Sum/Or type / multiple possible variants
157//! pub enum OrderCommand {
158//!     Create(CreateOrderCommand),
159//!     Update(UpdateOrderCommand),
160//!     Cancel(CancelOrderCommand),
161//! }
162//! // models Product/And type / a concrete variant, consisting of named fields
163//! pub struct CreateOrderCommand {
164//!     pub order_id: u32,
165//!     pub customer_name: String,
166//!     pub items: Vec<String>,
167//! }
168//! // models Product/And type / a concrete variant, consisting of named fields
169//! pub struct UpdateOrderCommand {
170//!     pub order_id: u32,
171//!     pub new_items: Vec<String>,
172//! }
173//! // models Product/And type / a concrete variant, consisting of named fields
174//! pub struct CancelOrderCommand {
175//!     pub order_id: u32,
176//! }
177//!
178//! // ### `E` / Event / Fact
179//!
180//! // models Sum/Or type / multiple possible variants
181//! pub enum OrderEvent {
182//!     Created(OrderCreatedEvent),
183//!     Updated(OrderUpdatedEvent),
184//!     Cancelled(OrderCancelledEvent),
185//! }
186//! // models Product/And type / a concrete variant, consisting of named fields
187//! pub struct OrderCreatedEvent {
188//!     pub order_id: u32,
189//!     pub customer_name: String,
190//!     pub items: Vec<String>,
191//! }
192//! // models Product/And type / a concrete variant, consisting of named fields
193//! pub struct OrderUpdatedEvent {
194//!     pub order_id: u32,
195//!     pub updated_items: Vec<String>,
196//! }
197//! // models Product/And type / a concrete variant, consisting of named fields
198//! pub struct OrderCancelledEvent {
199//!     pub order_id: u32,
200//! }
201//!
202//! // ### `S` / State / Current state of the system/aggregate/entity
203//! #[derive(Clone)]
204//! struct OrderState {
205//!     order_id: u32,
206//!     customer_name: String,
207//!     items: Vec<String>,
208//!     is_cancelled: bool,
209//! }
210//!
211//! // ## Modeling the Behaviour of our domain
212//! //
213//! //  - algebraic data types form the structure of our entities (commands, state, and events).
214//! //  - functions/lambda offers the algebra of manipulating the entities in a compositional manner, effectively modeling the behavior.
215//! //
216//! // This leads to modularity in design and a clear separation of the entity’s structure and functions/behaviour of the entity.
217//! //
218//! // Fmodel library offers generic and abstract components to specialize in for your specific case/expected behavior
219//!
220//! fn decider<'a>() -> Decider<'a, OrderCommand, OrderState, OrderEvent> {
221//!     Decider {
222//!         // Your decision logic goes here.
223//!         decide: Box::new(|command, state| match command {
224//!             // Exhaustive pattern matching on the command
225//!             OrderCommand::Create(create_cmd) => {
226//!                 Ok(vec![OrderEvent::Created(OrderCreatedEvent {
227//!                     order_id: create_cmd.order_id,
228//!                     customer_name: create_cmd.customer_name.to_owned(),
229//!                     items: create_cmd.items.to_owned(),
230//!                 })])
231//!             }
232//!             OrderCommand::Update(update_cmd) => {
233//!                 // Your validation logic goes here
234//!                 if state.order_id == update_cmd.order_id {
235//!                     Ok(vec![OrderEvent::Updated(OrderUpdatedEvent {
236//!                         order_id: update_cmd.order_id,
237//!                         updated_items: update_cmd.new_items.to_owned(),
238//!                     })])
239//!                 } else {
240//!                     // In case of validation failure, return empty list of events or error event
241//!                     Ok(vec![])
242//!                 }
243//!             }
244//!             OrderCommand::Cancel(cancel_cmd) => {
245//!                 // Your validation logic goes here
246//!                 if state.order_id == cancel_cmd.order_id {
247//!                     Ok(vec![OrderEvent::Cancelled(OrderCancelledEvent {
248//!                         order_id: cancel_cmd.order_id,
249//!                     })])
250//!                 } else {
251//!                     // In case of validation failure, return empty list of events or error event
252//!                     Ok(vec![])
253//!                 }
254//!             }
255//!         }),
256//!         // Evolve the state based on the event(s)
257//!         evolve: Box::new(|state, event| {
258//!             let mut new_state = state.clone();
259//!             // Exhaustive pattern matching on the event
260//!             match event {
261//!                 OrderEvent::Created(created_event) => {
262//!                     new_state.order_id = created_event.order_id;
263//!                     new_state.customer_name = created_event.customer_name.to_owned();
264//!                     new_state.items = created_event.items.to_owned();
265//!                 }
266//!                 OrderEvent::Updated(updated_event) => {
267//!                     new_state.items = updated_event.updated_items.to_owned();
268//!                 }
269//!                 OrderEvent::Cancelled(_) => {
270//!                     new_state.is_cancelled = true;
271//!                 }
272//!             }
273//!             new_state
274//!         }),
275//!         // Initial state
276//!         initial_state: Box::new(|| OrderState {
277//!             order_id: 0,
278//!             customer_name: "".to_string(),
279//!             items: Vec::new(),
280//!             is_cancelled: false,
281//!         }),
282//!     }
283//! }
284//! ```
285//!
286//! ## Examples
287//!
288//! - [Restaurant Demo - with Postgres](https://github.com/fraktalio/fmodel-rust-demo)
289//! - [Gift Card Demo - with Axon](https://!github.com/AxonIQ/axon-rust/tree/main/gift-card-rust)
290//! - [FModel Rust Tests](https://!github.com/fraktalio/fmodel-rust/tree/main/tests)
291//!
292//! ## GitHub
293//!
294//! - [FModel Rust](https://!github.com/fraktalio/fmodel-rust)
295//!
296//! ## FModel in other languages
297//!
298//!  - [FModel Kotlin](https://!github.com/fraktalio/fmodel/)
299//!  - [FModel TypeScript](https://!github.com/fraktalio/fmodel-ts/)
300//!  - [FModel Java](https://!github.com/fraktalio/fmodel-java/)
301//!
302//! ## Credits
303//!
304//! Special credits to `Jérémie Chassaing` for sharing his [research](https://!www.youtube.com/watch?v=kgYGMVDHQHs)
305//! and `Adam Dymitruk` for hosting the meetup.
306//!
307//! ---
308//! Created with `love` by [Fraktalio](https://!fraktalio.com/)
309
310use decider::Decider;
311use saga::Saga;
312use serde::{Deserialize, Serialize};
313use view::View;
314
315/// Aggregate module - belongs to the `Application` layer - composes pure logic and effects (fetching, storing)
316pub mod aggregate;
317/// Decider module - belongs to the `Domain` layer - pure decision making component - pure logic
318pub mod decider;
319/// Materialized View module - belongs to the `Application` layer - composes pure event handling algorithm and effects (fetching, storing)
320pub mod materialized_view;
321/// Saga module - belongs to the `Domain` layer - pure mapper of action results/events into new actions/commands
322pub mod saga;
323/// Saga Manager module - belongs to the `Application` layer - composes pure saga and effects (publishing)
324pub mod saga_manager;
325/// Given-When-Then Test specificatin domain specific language - unit testing
326pub mod specification;
327/// View module - belongs to the `Domain` layer - pure event handling algorithm
328pub mod view;
329
330/// The [DecideFunction] function is used to decide which events to produce based on the command and the current state.
331pub type DecideFunction<'a, C, S, E, Error> =
332    Box<dyn Fn(&C, &S) -> Result<Vec<E>, Error> + 'a + Send + Sync>;
333/// The [EvolveFunction] function is used to evolve the state based on the current state and the event.
334pub type EvolveFunction<'a, S, E> = Box<dyn Fn(&S, &E) -> S + 'a + Send + Sync>;
335/// The [InitialStateFunction] function is used to produce the initial state.
336pub type InitialStateFunction<'a, S> = Box<dyn Fn() -> S + 'a + Send + Sync>;
337/// The [ReactFunction] function is used to decide what actions/A to execute next based on the action result/AR.
338pub type ReactFunction<'a, AR, A> = Box<dyn Fn(&AR) -> Vec<A> + 'a + Send + Sync>;
339
340/// Generic Combined/Sum Enum of two variants
341#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
342pub enum Sum<A, B> {
343    /// First variant
344    First(A),
345    /// Second variant
346    Second(B),
347}
348
349/// Generic Combined/Sum Enum of three variants
350#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
351pub enum Sum3<A, B, C> {
352    /// First variant
353    First(A),
354    /// Second variant
355    Second(B),
356    /// Third variant
357    Third(C),
358}
359
360#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
361/// Generic Combined/Sum Enum of four variants
362pub enum Sum4<A, B, C, D> {
363    /// First variant
364    First(A),
365    /// Second variant
366    Second(B),
367    /// Third variant
368    Third(C),
369    /// Fourth variant
370    Fourth(D),
371}
372
373#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
374/// Generic Combined/Sum Enum of five variants
375pub enum Sum5<A, B, C, D, E> {
376    /// First variant
377    First(A),
378    /// Second variant
379    Second(B),
380    /// Third variant
381    Third(C),
382    /// Fourth variant
383    Fourth(D),
384    /// Fifth variant
385    Fifth(E),
386}
387
388#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
389/// Generic Combined/Sum Enum of six variants
390pub enum Sum6<A, B, C, D, E, F> {
391    /// First variant
392    First(A),
393    /// Second variant
394    Second(B),
395    /// Third variant
396    Third(C),
397    /// Fourth variant
398    Fourth(D),
399    /// Fifth variant
400    Fifth(E),
401    /// Sixth variant
402    Sixth(F),
403}
404
405/// Convenient type alias that represents 3 combined Deciders
406type Decider3<'a, C1, C2, C3, S1, S2, S3, E1, E2, E3, Error> =
407    Decider<'a, Sum3<C1, C2, C3>, (S1, S2, S3), Sum3<E1, E2, E3>, Error>;
408
409/// Convenient type alias that represents 4 combined Deciders
410type Decider4<'a, C1, C2, C3, C4, S1, S2, S3, S4, E1, E2, E3, E4, Error> =
411    Decider<'a, Sum4<C1, C2, C3, C4>, (S1, S2, S3, S4), Sum4<E1, E2, E3, E4>, Error>;
412
413/// Convenient type alias that represents 5 combined Deciders
414type Decider5<'a, C1, C2, C3, C4, C5, S1, S2, S3, S4, S5, E1, E2, E3, E4, E5, Error> =
415    Decider<'a, Sum5<C1, C2, C3, C4, C5>, (S1, S2, S3, S4, S5), Sum5<E1, E2, E3, E4, E5>, Error>;
416
417/// Convenient type alias that represents 6 combined Deciders
418type Decider6<'a, C1, C2, C3, C4, C5, C6, S1, S2, S3, S4, S5, S6, E1, E2, E3, E4, E5, E6, Error> =
419    Decider<
420        'a,
421        Sum6<C1, C2, C3, C4, C5, C6>,
422        (S1, S2, S3, S4, S5, S6),
423        Sum6<E1, E2, E3, E4, E5, E6>,
424        Error,
425    >;
426
427/// Convenient type alias that represents 3 merged Views
428type View3<'a, S1, S2, S3, E> = View<'a, (S1, S2, S3), E>;
429
430/// Convenient type alias that represents 4 merged Deciders
431type View4<'a, S1, S2, S3, S4, E> = View<'a, (S1, S2, S3, S4), E>;
432
433/// Convenient type alias that represents 5 merged Deciders
434type View5<'a, S1, S2, S3, S4, S5, E> = View<'a, (S1, S2, S3, S4, S5), E>;
435
436/// Convenient type alias that represents 6 merged Deciders
437type View6<'a, S1, S2, S3, S4, S5, S6, E> = View<'a, (S1, S2, S3, S4, S5, S6), E>;
438
439/// Convenient type alias that represents 3 merged Sagas
440type Saga3<'a, AR, A1, A2, A3> = Saga<'a, AR, Sum3<A1, A2, A3>>;
441
442/// Convenient type alias that represents 4 merged Sagas
443type Saga4<'a, AR, A1, A2, A3, A4> = Saga<'a, AR, Sum4<A1, A2, A3, A4>>;
444
445/// Convenient type alias that represents 5 merged Sagas
446type Saga5<'a, AR, A1, A2, A3, A4, A5> = Saga<'a, AR, Sum5<A1, A2, A3, A4, A5>>;
447
448/// Convenient type alias that represents 6 merged Sagas
449type Saga6<'a, AR, A1, A2, A3, A4, A5, A6> = Saga<'a, AR, Sum6<A1, A2, A3, A4, A5, A6>>;
450
451/// Identify the state/command/event.
452/// It is used to identify the concept to what the state/command/event belongs to. For example, the `order_id` or `restaurant_id`.
453pub trait Identifier {
454    /// Returns the identifier of the state/command/event
455    fn identifier(&self) -> String;
456}
457
458impl<A, B> Identifier for Sum<A, B>
459where
460    A: Identifier,
461    B: Identifier,
462{
463    fn identifier(&self) -> String {
464        match self {
465            Sum::First(a) => a.identifier(),
466            Sum::Second(b) => b.identifier(),
467        }
468    }
469}