Skip to content

Releases: fraktalio/fmodel-rust

0.9.1

14 Sep 13:30
c9736e2

Choose a tag to compare

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-futures feature propagated to domain components to remove Send/Sync bound 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

14 Sep 09:00

Choose a tag to compare

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

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

06 Jul 11:50

Choose a tag to compare

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

Full Changelog: 0.8.0...0.8.1

0.8.0

29 Jan 20:11

Choose a tag to compare

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
  • EventSourcedOrchestratingAggregate added by @idugalic in #52
  • Identifier trait added - domain by @idugalic in #53
  • Added new merge/combine function for the View by @idugalic in #54
  • Added new merge/combine function for the Saga by @idugalic in #55
  • combine deprecated on saga and view, in favor of merge by @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

29 Dec 23:52

Choose a tag to compare

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

  • async fn in Traits - a new stable feature of Rust 1.75 by @idugalic in #20

Full Changelog: 0.6.0...0.7.0

Check the docs


Created with ❤️ by Fraktalio

0.6.0

29 Dec 22:53

Choose a tag to compare

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

Full Changelog: 0.5.0...0.6.0

Check the docs


Created with ❤️ by Fraktalio

0.5.0

15 Dec 16:28

Choose a tag to compare

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 combine function 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

03 Dec 12:23

Choose a tag to compare

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 combine algebra on the Decider, View and Saga by @idugalic in #13

Full Changelog: 0.3.0...0.4.0

Check the docs


Created with ❤️ by Fraktalio

0.3.0

19 Oct 17:30

Choose a tag to compare

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

New Contributors

Full Changelog: 0.2.0...0.3.0


Created with ❤️ by Fraktalio

0.2.0

21 Sep 12:13

Choose a tag to compare

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.

  • EventSourcedAggregate abstracted to use Traits on the fields, not concrete implementation of the Decider
  • MaterializedView abstracted to use Traits on the fields, not concrete implementation of the Decider
  • Enabling concurrent usage of the aggregate/materialized view in multi-threaded runtimes.

What's Changed

New Contributors

Full Changelog: 0.1.0...0.2.0


Created with ❤️ by Fraktalio