Releases: fraktalio/fmodel-rust
0.9.1
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
Requirements - Rust 1.75.0+
From version 0.7.0+, the library is using async fn in Traits feature, which is currently available only in stable Rust 1.75.0+.
What's Changed
not-send-futuresfeature propagated to domain components to removeSend/Syncbound by @idugalic in #74
You can enable the feature:
[dependencies]
fmodel-rust = { version = "0.9.0", features = ["not-send-futures"] }Once enabled, the not-send-futures will remove Send/Sync bounds from deep Domain components for better ergonomics and more flexibility.
Compare the signatures of domain functions in fmodel:
/// The [DecideFunction] function is used to decide which events to produce based on the command and the current state.
#[cfg(not(feature = "not-send-futures"))]
pub type DecideFunction<'a, C, S, E, Error> =
Box<dyn Fn(&C, &S) -> Result<Vec<E>, Error> + 'a + Send + Sync>;
/// The [EvolveFunction] function is used to evolve the state based on the current state and the event.
#[cfg(not(feature = "not-send-futures"))]
pub type EvolveFunction<'a, S, E> = Box<dyn Fn(&S, &E) -> S + 'a + Send + Sync>;
/// The [InitialStateFunction] function is used to produce the initial state.
#[cfg(not(feature = "not-send-futures"))]
pub type InitialStateFunction<'a, S> = Box<dyn Fn() -> S + 'a + Send + Sync>;
/// The [ReactFunction] function is used to decide what actions/A to execute next based on the action result/AR.
#[cfg(not(feature = "not-send-futures"))]
pub type ReactFunction<'a, AR, A> = Box<dyn Fn(&AR) -> Vec<A> + 'a + Send + Sync>;
/// The [DecideFunction] function is used to decide which events to produce based on the command and the current state.
#[cfg(feature = "not-send-futures")]
pub type DecideFunction<'a, C, S, E, Error> = Box<dyn Fn(&C, &S) -> Result<Vec<E>, Error> + 'a>;
/// The [EvolveFunction] function is used to evolve the state based on the current state and the event.
#[cfg(feature = "not-send-futures")]
pub type EvolveFunction<'a, S, E> = Box<dyn Fn(&S, &E) -> S + 'a>;
/// The [InitialStateFunction] function is used to produce the initial state.
#[cfg(feature = "not-send-futures")]
pub type InitialStateFunction<'a, S> = Box<dyn Fn() -> S + 'a>;
/// The [ReactFunction] function is used to decide what actions/A to execute next based on the action result/AR.
#[cfg(feature = "not-send-futures")]
pub type ReactFunction<'a, AR, A> = Box<dyn Fn(&AR) -> Vec<A> + 'a>;Full Changelog: 0.9.0...0.9.1
0.9.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
Requirements - Rust 1.75.0+
From version 0.7.0+, the library is using async fn in Traits feature, which is currently available only in stable Rust 1.75.0+.
What's Changed
- Update Rust crate tokio to v1.47.1 by @renovate[bot] in #69
- Update actions/checkout action to v5 by @renovate[bot] in #70
- Bump slab from 0.4.10 to 0.4.11 by @dependabot[bot] in #71
- Feature/not send futures by @idugalic in #72
- Update Rust crate serde to v1.0.221 by @renovate[bot] in #73
Fearless Concurrency
Concurrency and async programming do not require a multi-threaded environment. You can run async tasks on a single-threaded executor as well.
fmodel-rust supports both safe multi-threaded and ergonomic single-threaded scenarios without forcing users to pay for thread safety when they don't need it.
fmodel-rust lets you choose between multi-threaded async and single-threaded async via an exlusive feature flag.
| Single-threaded world | Multi-threaded world |
|---|---|
Rc<T> |
Arc<T> |
RefCell<T> |
Mutex<T> / RwLock<T> |
Rc<RefCell<T>> |
Arc<Mutex<T>> / Arc<RwLock<T>> |
Send bound futures/Async (multi-threaded executors)
[dependencies]
fmodel-rust = { version = "0.9.0" }If you don’t enable the feature, the default mode requires Send so your futures can safely hop between threads
This mode is designed for multi-threaded runtimes like tokio’s default executor, where futures may be scheduled on any worker thread.
Here, you typically wrap shared state in Arc<Mutex<T>> or Arc<RwLock<T>>.
Example of the concurrent execution of the aggregate in multi-threaded environment (Send bound futures):
struct InMemoryOrderEventRepository {
events: RwLock<Vec<(OrderEvent, i32)>>,
}
async fn es_test() {
let repository = InMemoryOrderEventRepository::new();
let aggregate = Arc::new(EventSourcedAggregate::new(
repository,
decider().map_error(|()| AggregateError::DomainError("Decider error".to_string())),
));
let aggregate1 = Arc::clone(&aggregate);
let aggregate2 = Arc::clone(&aggregate);
let handle1 = thread::spawn(|| async move {
let command = OrderCommand::Create(CreateOrderCommand {
order_id: 1,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
});
let result = aggregate1.handle(&command).await;
assert!(result.is_ok());
});
let handle2 = thread::spawn(|| async move {
let command = OrderCommand::Create(CreateOrderCommand {
order_id: 2,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
});
let result = aggregate2.handle(&command).await;
assert!(result.is_ok());
});
handle1.join().unwrap().await;
handle2.join().unwrap().await;
}Send free futures/Async (single-threaded executors)
[dependencies]
fmodel-rust = { version = "0.9.0", features = ["not-send-futures"] }This mode removes the Send bound from async traits.
It works well with single-threaded runtimes (tokio::task::LocalSet) and allows using lighter primitives like Rc<RefCell<T>>.
Example of the concurrent execution of the aggregate in single-threaded environment (Send free Futures):
struct InMemoryOrderEventRepository {
events: RefCell<Vec<(OrderEvent, i32)>>,
}
async fn es_test_not_send() {
let repository = InMemoryOrderEventRepository::new();
let aggregate = Rc::new(EventSourcedAggregate::new(
repository,
decider().map_error(|()| AggregateError::DomainError("Decider error".to_string())),
));
let aggregate2 = Rc::clone(&aggregate);
// Notice how we `move` here, which requires Rc (not ARc). If you do not move, Rc is not needed.
let task1 = async move {
let command = OrderCommand::Create(CreateOrderCommand {
order_id: 1,
customer_name: "Alice".to_string(),
items: vec!["Item1".to_string()],
});
let result = aggregate.handle(&command).await;
assert!(result.is_ok());
};
let task2 = async move {
let command = OrderCommand::Create(CreateOrderCommand {
order_id: 1,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
});
let result = aggregate2.handle(&command).await;
assert!(result.is_ok());
};
// Run both tasks concurrently on the same thread.
tokio::join!(task1, task2);
}Full Changelog: 0.8.1...0.9.0
0.8.1
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
Requirements - Rust 1.75.0+
From version 0.7.0+, the library is using async fn in Traits feature, which is currently available only in stable Rust 1.75.0+.
What's Changed
- Improved Test DSL/specification:
pretty_assertions::assert_eq!- improves readability of differences in complex structures.#[track_caller]- Show failure at the test line, not helper
- Update Rust crate derive_more to v2 by @renovate in #59
map- better ergonomics - f1 and f2 are owned now by @idugalic in #61- Update Rust crate serde to v1.0.219 by @renovate in #60
- Bump tokio from 1.43.0 to 1.43.1 by @dependabot in #64
- Update Rust crate tokio to v1.44.2 by @renovate in #65
- Update Rust crate tokio to v1.45.0 by @renovate in #66
- Update Rust crate tokio to v1.45.1 by @renovate in #67
- Update Rust crate tokio to v1.46.1 by @renovate in #68
An example of the test using fmodel test DSL:
#[test]
fn create_order_event_sourced_test() {
let create_order_command = CreateOrderCommand {
order_id: 2,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
};
// Test the OrderDecider (event-sourced)
DeciderTestSpecification::default()
.for_decider(self::order_decider()) // Set the decider
.given(vec![]) // no existing events
.when(OrderCommand::Create(create_order_command.clone())) // Create an Order
.then(vec![OrderEvent::Created(OrderCreatedEvent {
order_id: 1,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
})]);
}thread 'create_order_event_sourced_test' panicked at tests/decider_test.rs:122:10:
assertion failed: `(left == right)`: Actual and Expected events do not match!
Command: Create(CreateOrderCommand { order_id: 2, customer_name: "John Doe", items: ["Item 1", "Item 2"] })
Diff < left / right > :
[
Created(
OrderCreatedEvent {
< order_id: 2,
> order_id: 1,
customer_name: "John Doe",
items: [
"Item 1",
"Item 2",
],
},
),
]#[test]
fn create_order_state_stored_test() {
let create_order_command = CreateOrderCommand {
order_id: 2,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
};
// Test the OrderDecider (state stored)
DeciderTestSpecification::default()
.for_decider(self::order_decider()) // Set the decider
.given_state(None) // no existing state
.when(OrderCommand::Create(create_order_command.clone())) // Create an Order
.then_state(OrderState {
order_id: 1,
customer_name: "John Doe".to_string(),
items: vec!["Item 1".to_string(), "Item 2".to_string()],
is_cancelled: false,
});
}thread 'create_order_state_stored_test' panicked at tests/decider_test.rs:174:10:
assertion failed: `(left == right)`: Actual and Expected states do not match.
Command: Create(CreateOrderCommand { order_id: 2, customer_name: "John Doe", items: ["Item 1", "Item 2"] })
Diff < left / right > :
OrderState {
< order_id: 2,
> order_id: 1,
customer_name: "John Doe",
items: [
"Item 1",
"Item 2",
],
is_cancelled: false,
}New Contributors
- @dependabot made their first contribution in #64
Full Changelog: 0.8.0...0.8.1
0.8.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
Requirements - Rust 1.75.0+
From version 0.7.0+, the library is using async fn in Traits feature, which is currently available only in stable Rust 1.75.0+.
What's Changed
- Decider: decide fn returns Result by @idugalic in #51
EventSourcedOrchestratingAggregateadded by @idugalic in #52- Identifier trait added - domain by @idugalic in #53
- Added new
merge/combinefunction for the View by @idugalic in #54 - Added new
merge/combinefunction for the Saga by @idugalic in #55 combinedeprecated on saga and view, in favor ofmergeby @idugalic in #56- combineN and mergeN introduced by @idugalic in #57
- Added test Given-When-Then specification DSL by @idugalic in #58
Full Changelog: 0.7.0...0.8.0
0.7.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
Requirements - Rust 1.75.0+
From version 0.7.0+, the library is using async fn in Traits feature, which is currently available only in stable Rust 1.75.0+.
If you are using an older version of Rust, please use version 0.6.0 of the library. It depends on async-trait crate. Version 0.6.0 is not maintained anymore, only patched for security issues and bugs.
We encourage you to upgrade to stable Rust 1.75.0+. It mostly fallback to removing async-trait dependency and macro from the implementation. Check our tests
What's Changed
Full Changelog: 0.6.0...0.7.0
Check the docs
Created with ❤️ by Fraktalio
0.6.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
What's Changed
- Update Rust crate async-trait to 0.1.75 by @renovate in #18
- Orchestrating State Stored Aggregate added. by @idugalic in #19
- Update Rust crate tokio to 1.35.1 by @renovate in #17
Full Changelog: 0.5.0...0.6.0
Check the docs
Created with ❤️ by Fraktalio
0.5.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
What's Changed
- simplifying the
combinefunction by @idugalic in #15 - implementing delegation pattern on the application layer components. by @idugalic in #16
Full Changelog: 0.4.0...0.5.0
Check the docs
Created with ❤️ by Fraktalio
0.4.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
What's Changed
- ActionComputation trait added. Saga as implementation. by @idugalic in #12
- Adding
combinealgebra on theDecider,ViewandSagaby @idugalic in #13
Full Changelog: 0.3.0...0.4.0
Check the docs
Created with ❤️ by Fraktalio
0.3.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
What's Changed
- Feature/saga by @idugalic in #9
- Update Rust crate async-trait to 0.1.74 by @renovate in #10
- Rust doc improved
New Contributors
Full Changelog: 0.2.0...0.3.0
Created with ❤️ by Fraktalio
0.2.0
fmodel-rust library is optimized for Event sourcing, CQRS, and Domain Modeling.
The goal is to accelerate the development of compositional, safe, and ergonomic applications. Fmodel uses a powerful Rust type-system to bring functional and algebraic domain modeling to Rust.
We appreciate your feedback. Discuss it. Tweet it.
EventSourcedAggregateabstracted to use Traits on the fields, not concrete implementation of theDeciderMaterializedViewabstracted to use Traits on the fields, not concrete implementation of theDecider- Enabling concurrent usage of the aggregate/materialized view in multi-threaded runtimes.
What's Changed
- Configure Renovate by @renovate in #2
- Update Rust crate async-trait to 0.1.73 by @renovate in #3
- Update Rust crate tokio to 1.32.0 by @renovate in #4
- Update actions/checkout action to v4 by @renovate in #6
New Contributors
Full Changelog: 0.1.0...0.2.0
Created with ❤️ by Fraktalio