2 releases
new 0.1.2 | Jan 19, 2025 |
---|---|
0.1.1 | Jan 19, 2025 |
0.1.0 |
|
#476 in Rust patterns
85KB
1.5K
SLoC
xdi
Simple service dependency graph container implementation
-
Allow resolve nested service dependency graph
-
Support Transient
-
Support Singletone
-
Support Task local (singletone in task scope)
-
Support Thread local (singletone in thread scope)
-
Allow to map service into any other representation as simple like
.map_as(|service| SomeOther { x: service.x })
-
Allow to map service into trait object as siple like
.map_as_trait::<dyn SomeTrait>()
-
Resolve single (first) service by self or by any mapping
-
Resolve all service wich has requested representation, usefull for trait object
-
Non blocking for transient, single lock for singletone/task_local/thread_local init
-
Allow
!Send
+!Sync
for transient and thread_local -
Readable errors
-
Simple architecture (constructor -> scope -> mapping)
-
Allow global
ServiceProvider
registration -
Main test cases allowed in tests folder
use xdi::builder::DiBuilder;
use std::sync::{Arc, Mutex};
pub trait ISomeTrait {
fn get(&self) -> String;
}
pub struct SomeService {
pub payload: String
}
pub struct SomeServiceDeep {
pub nested_service: Arc<Mutex<SomeService>>
}
impl ISomeTrait for SomeServiceDeep {
fn get(&self) -> String {
self.nested_service.lock().unwrap().payload.clone()
}
}
pub struct SomeServiceDeeper {
pub nested_service: SomeServiceDeep
}
fn main() {
let builder = DiBuilder::new();
// register singletone
builder.singletone(|_| Ok(Arc::new(Mutex::new(SomeService { payload: "1".to_string() }))));
// register transient
builder.transient(|sp| Ok(SomeServiceDeeper { nested_service: sp.resolve()? }));
// register transient with mapping to trait
builder.transient(|sp| Ok(SomeServiceDeep { nested_service: sp.resolve()? }))
.map_as_trait::<dyn ISomeTrait>();
let sp = builder.build();
// automaticaly resolve all service dependency graph
// SomeServiceDeeper -> SomeServiceDeep -> Arc<Mutex<SomeService>>
let service = sp.resolve::<SomeServiceDeeper>().unwrap();
assert_eq!(service.nested_service.nested_service.lock().unwrap().payload, "1");
// change inner singletone
service.nested_service.nested_service.lock().unwrap().payload = "2".to_string();
// resolve dependency second time
// new SomeServiceDeeper and SomeServiceDeep, but old Arc<Mutex<SomeService>>
let service = sp.resolve::<SomeServiceDeeper>().unwrap();
assert_eq!(service.nested_service.nested_service.lock().unwrap().payload, "2");
// SomeServiceDeep also allowed as mapping into Box<dyn ISomeTrait>
let service = sp.resolve::<Box<dyn ISomeTrait>>().unwrap();
assert_eq!(service.get(), "2");
}
How to use
Create container builder
let builder = DiBuilder::new();
// or
let builder = DiBuilder::default();
Register the service
- Mutable access not required, builder can be shared by ref
- Registration fn takes used ServiceProvider and can resolve nested dependency
As transient
- Create new instance every call
- Allowed !Send + !Sync
pub struct DbConnection {}
pub struct Repository {
conn: DbConnection,
}
builder.transient(|_sp: ServiceProvider| Ok(DbConnection {}));
builder.transient(|sp: ServiceProvider| Ok(Repository {
conn: sp.resolve::<DbConnection>()?,
}));
As singletone
- Lazy creation on the first invocation and return a clone on every next invocation
- Singletone required clone for service (you can wrap to Arc or derive Clone)
- Singletone required Sync + Send because it can be shared anywhere
#[derive(Clone)]
pub struct SomeService {}
builder.singletone(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}));
As task local
- Lazy creation on the first invocation from the task scope and return a clone on every next invocation in same task scope
- Task local required clone for service (you can wrap to Arc or derive Clone)
- Task local required Sync + Send because it can be shared anywhere
#[cfg(feature = "task-local")]
{
#[derive(Clone)]
pub struct SomeService {}
builder.task_local(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}));
}
As thread local
- Lazy creation on the first invocation from the thread scope and return a clone on every next invocation in same thread scope
- Thread local required clone for service (you can wrap to Rc or derive Clone)
- Allowed !Send + !Sync
#[derive(Clone)]
pub struct SomeService {}
builder.thread_local(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}));
Map service
- Mapping allow add new service representation for same constructor
- Mapping (Service -> Service) auto-generated
- You can add as many mappings for a single service as you need
Custom map
pub struct DbConnectionInner {}
pub struct DbConnectionPool {}
impl DbConnectionPool {
fn get(&self) -> DbConnectionInner { DbConnectionInner {} }
}
pub struct DbConnection {
conn: DbConnectionInner,
}
builder.transient(|_sp: ServiceProvider| Ok(DbConnectionPool {
//... some initialization
}))
.map_as(|pool| Ok(DbConnection { conn: pool.get() }));
Trait object map
- Create mapping to
Box<dyn ISomeTrait>
if service impl ISomeTrait
builder.transient(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}))
.map_as_trait::<dyn ISomeTrait>();
Build container
- You can build container as var, or register global
Build container as var
let sp = builder.build();
Build and register global
builder.build_global();
// then access by static global var
let service = ServiceProvider::get().unwrap().resolve::<SomeService>().unwrap();
Resolve service by mapping
As service
let service: SomeService = sp.resolve().unwrap();
// let service: Box<dyn ISomeTrait> = sp.resolve().unwrap();
As boxed service
use xdi::types::type_info::TypeInfoSource;
let service = sp.resolve_raw(SomeService::type_info()).unwrap();
// let service = sp.resolve(Box::<dyn ISomeTrait>::type_info()).unwrap();
let service = service.unbox::<SomeService>().unwrap();
// let service = service.unbox::<Box<dyn ISomeTrait>>().unwrap();
As vector of services, which has some mapping
builder.transient(|_| Ok(SomeService {}))
.map_as_trait::<dyn ISomeTrait>();
builder.transient(|_| Ok(OtherService {}))
.map_as_trait::<dyn ISomeTrait>();
let services: Vec<Box<dyn ISomeTrait>> = sp.resolve_all().unwrap();
As vector of boxed services, which has some mapping
use xdi::types::{type_info::TypeInfoSource, boxed_service::BoxedService};
builder.transient(|_| Ok(SomeService {}))
.map_as_trait::<dyn ISomeTrait>();
builder.transient(|_| Ok(OtherService {}))
.map_as_trait::<dyn ISomeTrait>();
let services: Vec<BoxedService> = sp.resolve_all_raw(Box::<dyn ISomeTrait>::type_info()).unwrap();
As dependency in task scope
#[cfg(feature = "task-local")]
{
use xdi::IAsyncTaskScope;
builder.task_local(|_| Ok(SomeService {}));
let sp = builder.build();
let sp2 = sp.clone();
tokio::spawn(async move {
let service = sp.resolve::<SomeService>().unwrap();
// In second time resolve return instanse clone (like singletone)
let service = sp.resolve::<SomeService>().unwrap();
}.add_service_span());
tokio::spawn(async move {
// New task has own SomeService instance
let service = sp2.resolve::<SomeService>().unwrap();
}.add_service_span());
}
Dependencies
~2.3–9MB
~74K SLoC