Dependency injection for Rust
Run the following Cargo command in your project directory:
cargo add blackbox_di
Or add the following line to your Cargo.toml:
toml
blackbox_di = "0.1"
Annotate interface with #[interface]
:
```rust
trait IService { fn call(&self); } ```
Annotate service structure with #[injectable]
:
```rust
struct Service {} ```
Annotate impl block with #[implements]
:
```rust
impl IService for Service { fn call() { println!("Service calling"); } } ```
Annotate module structure with #[module]
and specify Service
structure as a provider
:
```rust
struct RootModule { #[provider] service: Service } ```
```rust
async fn launch() {
let app = build::
let service = app
.get_by_token::<Service>(&get_token::<Service>)
.unwrap();
// short (equivalent to above)
let service = app.get::<Service>().unwrap();
service.call();
} ```
Specify #[inject]
for injectable dependencies:
```rust
struct Repo {}
struct Service {
#[inject]
repo: Ref
Don't forget to specify the Repo
in the RootModule
module:
```rust
struct RootModule { #[provider] repo: Repo
#[provider] service: Service } ```
You can specify a token instead of a type:
```rust
struct Repo {}
struct Service {
#[inject("REPO_TOKEN")]
repo: Ref
```rust
struct RootModule { #[provider("REPO_TOKEN")] repo: Repo
#[provider] service: Service } ```
Or use a constant as a token:
```rust const REPOTOKEN: &str = "REPOTOKEN";
struct Repo {}
struct Service {
#[inject(REPO_TOKEN)]
repo: Ref
struct RootModule { #[provider(REPO_TOKEN)] repo: Repo
#[provider] service: Service } ```
You also can use interfaces for injectable dependencies:
```rust const REPOTOKEN: &str = "REPOTOKEN";
trait IRepo {}
struct Repo {}
impl IRepo for Repo {}
struct Service {
#[inject(REPO_TOKEN)]
repo: Ref
struct RootModule { #[provider(REPO_TOKEN)] repo: Repo
#[provider] service: Service } ```
Or just use an existing implementation of the interface:
```rust
trait IRepo {}
struct Repo {}
impl IRepo for Repo {}
struct Service {
#[inject(use Repo)]
repo: Ref
struct RootModule { #[provider] repo: Repo
#[provider] service: Service } ```
If a service has non-injection dependencies:
```rust
struct Service {
#[inject]
repo: Ref
greeting: String } ```
You should specify a factory function:
```rust
impl Service {
#[factory]
fn new(repo: Ref
Or for interfaces:
```rust
struct Service {
#[inject(use Repo)]
repo: Ref
greeting: String }
impl Service {
#[factory]
fn new(repo: Ref
Injectable services with non-injectable
dependencies must have the factory
functions.
To have mutable non-injectable deps, you need specify these dependencies with RefMut<...>
:
```rust
struct Service {
#[inject(use Repo)]
repo: Ref
greeting: RefMut
impl Service {
#[factory]
fn new(repo: Ref
fn setgreeting(&self, msg: String) { *self.greeting.asmut() = msg; }
fn printgreeting(&self) { println!("{}", self.greeting.asref()); } } ```
You can specify multiple modules and import them:
```rust
struct UserModule { #[provider] user_service: UserService, }
struct RootModule { #[import] user_module: UserModule } ```
To use providers from imported modules you should specify these providers as exported
:
```rust
struct UserModule { #[provider] #[export] user_service: UserService, } ```
Also, you can specify your modules as global
then you don't have to import their directly. Just specify their only in the root
module:
```rust
struct UserModule { #[provider] #[export] user_service: UserService, }
struct AccountModule { #[provider] #[export] account_service: AccountService, }
struct RootModule { #[import] user_module: UserModule
#[import] account_module: AccountModule } ```
To resolve dependency cycle use Lazy
when module importing:
```rust
struct UserModule {
#[import]
account_module: Lazy
#[provider] #[export] user_service: UserService, }
struct AccountModule {
#[import]
user_module: Lazy
#[provider] #[export] account_service: AccountService, }
struct RootModule { #[import] user_module: UserModule
#[import] account_module: AccountModule } ```
When the container is fully initialized, the system triggers events on_module_init
:
```rust
impl OnModuleInit for Service { async fn onmoduleinit(&self) { ... } } ```
and on_module_destroy
:
```rust
impl OnModuleDestroy for Service { async fn onmoduledestroy(&self) { ... } } ```
The logger is used to display information about app build. To use custom logger implement ILogger
trait:
rust
pub trait ILogger {
fn log<'a>(&self, level: LogLevel, msg: &'a str);
fn log_with_ctx<'a>(&self, level: LogLevel, msg: &'a str, ctx: &'a str);
fn set_context<'a>(&self, ctx: &'a str);
fn get_context(&self) -> String;
}
And then change build params:
```rust
let app = build::
let custom_logger = app.get::
app.uselogger(customlogger.cast::
BlackBox DI is licensed under: