Rust ioc system
The library is for deep tree of dependencies
Advantages:
Deficiencies:
Library required Rust nightly for trait as interface (Unsize)
The components source is DependencyContext
``` rust /* creating root DependencyContext */
fn () { let rootcontext = DependencyContext::new_root() } ```
There are several ways to register components
``` rust /* all way for component registration */
async fn () { //let rootcontext = DependencyContext::new_root()
// adds component, which implement Constructor trait
root_context.register_type::<SomeComponent>(DependencyLifeCycle::Transient).await.unwrap();
// adds component from closure
root_context.register_closure(|_| Ok(SomeComponent {}), DependencyLifeCycle::Transient).await.unwrap();
// adds component from async closure
root_context.register_async_closure(
move |_: crate::DependencyContext| { async move { Ok(SomeComponent {}) }},
DependencyLifeCycle::Transient
).await.unwrap();
// adds an existing component
root_context.register_instance(RwLock::new(instance)).await.unwrap();
} ```
Each type of component registration (except instance registration) takes 3 life times
Instance registration is always a Singleton
```rust /* Life times */
async fn () { //let rootcontext = DependencyContext::new_root()
// instance per call
root_context.register_type::<SomeComponent>(DependencyLifeCycle::Transient).await.unwrap();
// single instance
root_context.register_type::<SomeComponent>(DependencyLifeCycle::Singleton).await.unwrap();
// instance per scope
root_context.register_type::<SomeComponent>(DependencyLifeCycle::Scoped).await.unwrap();
}
```
To register a component through a type, you need to implement Constructor trait
``` rust /* Constructor implementation */
impl Constructor for SomeComponent {
async fn ctor(_: crate::DependencyContext) -> BuildDependencyResult
Components are given a unique context when they are built
You can store the context in a struct field and get dependencies anywhere in the component
Nested (child) dependencies can be requested from the context
``` rust /* Resolve nested service */
impl Constructor for SomeComponent {
async fn ctor(ctx: crate::DependencyContext) -> BuildDependencyResult
Components context is the same as the root context, which means it can register dependencies
``` rust /* register new dependency */
impl Constructor for SomeComponent {
async fn ctor(ctx: crate::DependencyContext) -> BuildDependencyResult
Ok( Self {
nested_service1: ctx.resolve::<SomeComponent2>().await?,
nested_service2: ctx.resolve().await?
} )
}
} ```
You can resolve the first (by TypeId) or all matching dependencies
``` rust /* dependency resolving way */
async fn () {
//let rootcontext = DependencyContext::newroot()
//rootcontext.register_type::
// return first (by TypeId) component
let mut dependency = root_context.resolve::<SomeComponent>().await.unwrap();
// return all match as Vector<SomeComponent> (look at service mappings section)
let mut dependency_vector = root_context.resolve_collection::<Box<dyn SomeTrait>>().await.unwrap();
}
```
Each life time resolve in a different way
``` rust /* dependency resolving match */
async fn () {
//let rootcontext = DependencyContext::newroot()
//rootcontext.registertype::
// resolve transient
let mut dependency = root_context.resolve::<SomeComponent1>().await.unwrap();
// resolve singleton
let mut dependency2 = root_context.resolve::<Arc<SomeComponent2>>().await.unwrap();
// resolve scoped
let mut dependency3 = root_context.resolve::<Weak<SomeComponent3>>().await.unwrap();
// To get a mutable singleton you need to register with RwLock/Lock
// Constructor trait implemented by default for tokio::sync::RwLock<T>, std::sync::RwLock<T>
} ```
You can map a component to a service
By default, only the component's mapping to itself is created
You are not limited in the number of mappings
``` rust /* component to service mapping */
async fn () { //let rootcontext = DependencyContext::new_root()
// mapping at creation time
root_context.register_type::<SomeComponent>(DependencyLifeCycle::Transient).await.unwrap()
.map_as::<dyn SomeImplementedTrait1>().await.unwrap();
// map after creation
root_context.map_component::<SomeComponent, dyn SomeImplementedTrait2>().await.unwrap();
} ```
Service is resolved in Box\ ``` rust
/* scope resolving */ async fn () {
//let rootcontext = DependencyContext::newroot()
//rootcontext.registertype:: }
``` Scoped components live until all Arc of scope are removed If the parent changes scope before the child instance is created, the child instance will be created with the new scope created by the parent You can always create a new scope, or set an old one ``` rust
/* Scope manipulation */ impl Constructor for SomeComponent {
async fn ctor(ctx: crate::DependencyContext) -> BuildDependencyResult }
``` Global context verifies link of the requested dependencies and return error in case of a circular dependency You can debug inner state: ``` rust
async fn () {
let rootcontext = DependencyContext::newroot()
rootcontext.registertype:: } ```rust
use asynctrait::asynctrait; use anthill_di::{
Constructor,
types::BuildDependencyResult,
DependencyContext,
DependencyLifeCycle
}; struct TransientDependency1 {
pub d1: TransientDependency2,
pub d2: TransientDependency2,
} impl Constructor for TransientDependency1 {
async fn ctor(ctx: DependencyContext) -> BuildDependencyResult struct TransientDependency2 {
pub str: String,
} impl Constructor for TransientDependency2 {
async fn ctor(: DependencyContext) -> BuildDependencyResult fn main() {
let rootcontext = DependencyContext::newroot();
rootcontext.registertype:: }
```let service = root_context.resolve::<Box<dyn SomeImplementedTrait>>().await.unwrap();
Child instance contain scope of parent [async_trait]
//instance from old scope
let instance1 = ctx.resolve::<SomeInstance>().await?;
// return new scope after create
let new_scope = ctx.set_empty_scope();
//instance from new scope
let instance2 = ctx.resolve::<SomeInstance>().await?;
// set old scope
ctx.set_scope(old_scope);
//instance from old scope
let instance3 = ctx.resolve::<SomeInstance>().await?;
Ok( Self { } )
}
If the check is successful, all subsequent requests for this pair link will not check for cycling
println!("{root_context:#?}");
```
Basic example
[async_trait]
[async_trait]
[tokio::main]
let dependency = root_context.resolve::<TransientDependency1>().await.unwrap();
assert_eq!(dependency.d1.str, "test".to_string());
assert_eq!(dependency.d2.str, "test".to_string());
More shared examples present in src/tests folder
Little architecture overview
Refs