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§
- Async
Group - The structure that allows for the asynchronous execution of multiple functions and waits for all of them to complete.
- Auto
Shutdown - 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§
- Async
Group Error - The enum type representing the reasons for errors that can occur within an
AsyncGroup
. - Data
HubError - 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.
- Data
Conn - 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.