Crate sabi

Source
Expand description

This crate provides a small framework for Rust, designed to separate application logic from data access.

In this framework, the logic exclusively takes a data access trait as its argument, and all necessary data access is defined by a single data access trait. Conversely, the concrete implementations of data access methods are provided as default methods of DataAcc derived traits, allowing for flexible grouping, often by data service.

The DataHub bridges these two parts. It attaches all DataAcc derived traits, and then, using the override_macro crate, it overrides the methods of the data access trait used by the logic to point to the implementations found in the DataAcc derived traits. This clever use of this macro compensates for Rust’s lack of native method overriding, allowing the logic to interact with data through an abstract interface.

Furthermore, the DataHub provides transaction control for data operations performed within the logic. You can execute logic functions with transaction control using the txn! macro, or without transaction control using the run! macro. Furthermore, you can execute asynchronous logic function with transaction control using txn_async! macro or without transaction control using the run_async! macro.

This framework brings clear separation and robustness to Rust application design.

§Example

The following is a sample code using this framework:

use sabi::{AsyncGroup, DataSrc, DataConn, DataAcc, DataHub};
use errs::Err;
use override_macro::{overridable, override_with};

// (1) Implements DataSrc(s) and DataConn(s).

struct FooDataSrc { /* ... */ }

impl DataSrc<FooDataConn> for FooDataSrc {
    fn setup(&mut self, ag: &mut AsyncGroup) -> Result<(), Err> { /* ... */ Ok(()) }
    fn close(&mut self) { /* ... */ }
    fn create_data_conn(&mut self) -> Result<Box<FooDataConn>, Err> {
        Ok(Box::new(FooDataConn{ /* ... */ }))
    }
}

struct FooDataConn { /* ... */ }

impl FooDataConn { /* ... */ }

impl DataConn for FooDataConn {
    fn commit(&mut self, ag: &mut AsyncGroup) -> Result<(), Err> { /* ... */ Ok(()) }
    fn rollback(&mut self, ag: &mut AsyncGroup) { /* ... */ }
    fn close(&mut self) { /* ... */ }
}

struct BarDataSrc { /* ... */ }

impl DataSrc<BarDataConn> for BarDataSrc {
    fn setup(&mut self, ag: &mut AsyncGroup) -> Result<(), Err> { /* ... */ Ok(()) }
    fn close(&mut self) { /* ... */ }
    fn create_data_conn(&mut self) -> Result<Box<BarDataConn>, Err> {
        Ok(Box::new(BarDataConn{ /* ... */ }))
    }
}

struct BarDataConn { /* ... */ }

impl BarDataConn { /* ... */ }

impl DataConn for BarDataConn {
    fn commit(&mut self, ag: &mut AsyncGroup) -> Result<(), Err> { /* ... */ Ok(()) }
    fn rollback(&mut self, ag: &mut AsyncGroup) { /* ... */ }
    fn close(&mut self) { /* ... */ }
}

// (2) Implements logic functions and data traits

#[overridable]
trait MyData {
    fn get_text(&mut self) -> Result<String, Err>;
    fn set_text(&mut self, text: String) -> Result<(), Err>;
}

fn my_logic(data: &mut impl MyData) -> Result<(), Err> {
    let text = data.get_text()?;
    let _ = data.set_text(text)?;
    Ok(())
}

// (3) Implements DataAcc(s)

#[overridable]
trait GettingDataAcc: DataAcc {
    fn get_text(&mut self) -> Result<String, Err> {
        let conn = self.get_data_conn::<FooDataConn>("foo")?;
        /* ... */
        Ok("output text".to_string())
    }
}

#[overridable]
trait SettingDataAcc: DataAcc {
    fn set_text(&mut self, text: String) -> Result<(), Err> {
        let conn = self.get_data_conn::<BarDataConn>("bar")?;
        /* ... */
        Ok(())
    }
}

// (4) Consolidate data traits and DataAcc traits to a DataHub.

impl GettingDataAcc for DataHub {}
impl SettingDataAcc for DataHub {}

#[override_with(GettingDataAcc, SettingDataAcc)]
impl MyData for DataHub {}

// (5) Use the logic functions and the DataHub

fn main() {
    // Register global DataSrc.
    sabi::uses("foo", FooDataSrc{});
    // Set up the sabi framework.
    // _auto_shutdown automatically closes and drops global DataSrc at the end of the scope.
    // NOTE: Don't write as `let _ = ...` because the return variable is dropped immediately.
    let _auto_shutdown = sabi::setup().unwrap();

    // Create a new instance of DataHub.
    let mut data = DataHub::new();
    // Register session-local DataSrc with DataHub.
    data.uses("bar", BarDataSrc{});

    // Execute application logic within a transaction.
    // my_logic performs data operations via DataHub.
    let _ = sabi::txn!(my_logic, data).unwrap();
}

If you want to run this framework within an async function/block, you should use setup_async instead of setup, run_async instead of run, and txn_async instead of txn, as shown below.

async fn my_logic(data: &mut impl MyData) -> Result<(), Err> {
    let text = data.get_text()?;
    let _ = data.set_text(text)?;
    Ok(())
}

#[tokio::main]
async fn main() {
    // Register global DataSrc.
    sabi::uses("foo", FooDataSrc{}).await;
    // Set up the sabi framework.
    // _auto_shutdown automatically closes and drops global DataSrc at the end of the scope.
    // NOTE: Don't write as `let _ = ...` because the return variable is dropped immediately.
    let _auto_shutdown = sabi::setup_async().await.unwrap();

    // Create a new instance of DataHub.
    let mut data = DataHub::new();
    // Register session-local DataSrc with DataHub.
    data.uses("bar", BarDataSrc{});

    // Execute application logic within a transaction.
    // my_logic performs data operations via DataHub.
    let _ = sabi::txn_async!(my_logic, data).await.unwrap();
}

Macros§

run
Executes a given logic function without transaction control.
run_async
Executes asynchronously a given logic function without transaction control.
txn
Executes a given logic function within a transaction.
txn_async
Executes asynchronously a given logic function within a transaction.

Structs§

AsyncGroup
The structure that allows for the asynchronous execution of multiple functions and waits for all of them to complete.
AutoShutdown
A utility struct that ensure to close and drop global data sources when it goes out scope.
DataHub
The struct that acts as a central hub for data input/output operations, integrating multiple Data traits (which are passed to business logic functions as their arguments) with DataAcc traits (which implement default data I/O methods for external services).

Enums§

AsyncGroupError
The enum type representing the reasons for errors that can occur within an AsyncGroup.
DataHubError
An enum type representing the reasons for errors that can occur within DataHub operations.

Traits§

DataAcc
The trait that aggregates data access operations to external data services into logical units, with methods providing default implementations.
DataConn
The trait that abstracts a connection per session to an external data service, such as a database, file system, or messaging service.
DataSrc
The trait that abstracts a data source responsible for managing connections to external data services, such as databases, file systems, or messaging services.

Functions§

setup
Executes the setup process for all globally registered data sources.
setup_async
Executes asynchronously the setup process for all globally registered data sources.
uses
Registers a global data source that can be used throughout the application.