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//! 
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//! 
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}