DDI (dynamic dependency injection)

This library provides a generic dependency injection container that can be easily integrated into any application and can be extremely extensible with the extension trait.

Dependency injection is a common design pattern , mainly used in some frameworks such as Rocket, Actix Web, bevy. With ddi you can implement dependency injection without such frameworks, and you can implement your own framework.

Example

```rust use ddi::*;

struct TestService(String);

let mut services = ServiceCollection::new(); services.service(1usize); services.service("helloworld"); services.service_factory(|num: &usize, str: &&str| Ok(TestService(format!("{}{}", num, str))));

let provider = services.provider(); assert_eq!(provider.get::().unwrap().0, "1helloworld"); ```

std feature

ddi supports no-std by default, if std feature enabled the internal data structure will be changed from [alloc::collections::BTreeMap] to [std::collections::HashMap] and [std::error::Error] will be implemented for [DDIError]. This will give a little performance improvement and usability.

sync feature

If sync feature enabled, ddi will support multi-threading and you can share [ServiceProvider] between multiple threads.

! Enabling sync may cause your existing code to not compile! This is because enabling sync requires instances in the [ServiceCollection] to implement send + sync and ServiceFactory to implement send. And default no such restrictions.

Basic Usage

First you need to register all services in the [ServiceCollection], which is a container of all services, [ServiceCollection] stored a series of triplets (type, name, implementation). You can use the [ServiceCollection::service] to add item to it.

For example, the following code will add a item (&str, "default", "helloworld") to the [ServiceCollection]

rust ignore let mut services = ServiceCollection::new(); services.service("helloworld");

Here, the service "implementation" can also be a function, the factory of the service. The factory function is lazy execution, will only be executed when the service is used. For example.

rust ignore services.service_factory(|| Ok("helloworld"));

The service factory can use parameters to get other services as dependencies. ddi will pass in the corresponding services based on the type of the parameters. Due to the reference rule of rust, the type of the parameter must be an immutable reference type.

rust ignore services.service_factory(|dep: &Foo| Ok(Bar::new(dep)));

When you have all the services registered, use [ServiceCollection::provider()] to get the [ServiceProvider], and then you can get any service you want from [ServiceProvider].

rust ignore let provider = services.provider(); assert_eq!(provider.get::<TestService>().unwrap().0, "helloworld");

Design Patterns

* Wrap your service with [Service<T>] (Arc)

When a service wants to hold references to other services, the referenced service should be wrapped in [Arc<T>] for proper lifecycle handling. ddi defines an alias type Service<T> = Arc<T> for such a pattern.

We recommend wrapping all services in [Service<T>] to make cross-references between services easier.

That does not allow circular references, because ddi does not allow circular dependencies, which would cause the [DDIError::CircularDependencyDetected] error.

```rust use ddi::*;

struct Bar; struct Foo(Service);

let mut services = ServiceCollection::new(); services.service(Service::new(Bar)); services.service_factory( |bar: &Service| Ok(Service::new(Foo(bar.clone()))) );

let provider = services.provider(); assert!(provider.get::>().is_ok()); ```

* Use extension trait to expanding [ServiceCollection]

The extension trait makes [ServiceCollection] extremely extensible. The following example shows the use of the extension trait to register multiple services into one function.

```rust use ddi::*;

// ------------ definition ------------

[derive(Clone)]

struct DbConfiguration; struct DbService(DbConfiguration, Service); struct DbConnectionManager;

pub trait DbServiceCollectionExt: ServiceCollectionExt { fn installdatabase(&mut self) { self.service(Service::new(DbConnectionManager)); self.servicefactory( |config: &DbConfiguration, cm: &Service| Ok(Service::new(DbService(config.clone(), cm.clone()))) ); self.service(DbConfiguration); } }

impl DbServiceCollectionExt for T {}

// -------------- usage ---------------

let mut services = ServiceCollection::new();

services.install_database();

let provider = services.provider(); assert!(provider.get::>().is_ok()); ```

* Use [ServiceProvider] in the factory, get other services dynamically

In our previous examples service factory used static parameters to get the dependencies, in the following example we use [ServiceProvider] to get the dependencies dynamically.

```rust use ddi::*;

trait Decoder: Send + Sync { fn name(&self) -> &'static str; } struct HardwareDecoder; struct SoftwareDecoder; impl Decoder for HardwareDecoder { fn name(&self) -> &'static str { "hardware" } } impl Decoder for SoftwareDecoder { fn name(&self) -> &'static str { "software" } } struct Playback { decoder: Service }

const SUPPORTHARDWAREDECODER: bool = false;

let mut services = ServiceCollection::new();

if SUPPORTHARDWAREDECODER { services.service(Service::new(HardwareDecoder)); } services.service(Service::new(SoftwareDecoder)); services.service_factory( |provider: &ServiceProvider| { if let Ok(hardware) = provider.get::>() { Ok(Playback { decoder: hardware.clone() }) } else { Ok(Playback { decoder: provider.get::>()?.clone() }) } } );

let provider = services.provider(); assert_eq!(provider.get::().unwrap().decoder.name(), "software"); ```

* Use service_var or service_factory_var to register variants of service

The [ServiceCollection] can register multiple variants of the same type of service, using service_var or service_factory_var. When registering variants you need to declare ServiceName for each variant, the default (registered using the service or service_factory function) ServiceName is "default".

The following example demonstrates how to build an http server based on service variants.

[doc(cfg(not(feature = "sync")))]

```rust use ddi::*;

type Route = Service String>; struct HttpService { routes: std::collections::HashMap } struct BusinessService { value: String }

let mut services = ServiceCollection::new();

services.servicevar("/index", Service::new(|| "".tostring()) as Route); services.servicevar("/404", Service::new(|| "404".tostring()) as Route); services.servicefactoryvar( "/business", |business: &Service| { let ownedbusiness = business.clone(); Ok(Service::new(move || ownedbusiness.value.clone()) as Route) } ); services.servicefactory( |provider: &ServiceProvider| { let routes = provider.getall::()? .intoiter() .map(|(path, route)| (path.tostring(), route.clone())) .collect(); Ok(HttpService { routes }) } ); services.service(Service::new(BusinessService { value: "hello".to_string() }));

let provider = services.provider(); asserteq!(provider.get::().unwrap().routes.get("/index").unwrap()(), ""); asserteq!(provider.get::().unwrap().routes.get("/404").unwrap()(), "404"); assert_eq!(provider.get::().unwrap().routes.get("/business").unwrap()(), "hello"); ```

* Use extension trait to simplify the registration of service variants

In the previous example we used service_var and service_factory_var to register routes for the http server, but the code were obscure and no type checking. The following example demonstrates use extension trait to simplify the definition of routes and solve these problems.

```rust use ddi::*;

// ------------ definition ------------

type Route = Service String>; struct HttpService { routes: std::collections::HashMap } struct BusinessService { value: String }

pub trait HttpCollectionExt: ServiceCollectionExt { fn installhttp(&mut self) { self.servicefactory( |provider: &ServiceProvider| { let routes = provider.getall::()? .intoiter() .map(|(path, route)| (path.to_string(), route.clone())) .collect(); Ok(HttpService { routes }) } ); }

fn install_route(&mut self, path: &'static str, route: impl Fn() -> String + 'static) {
  self.service_var(path, Service::new(route) as Route);
}

fn install_route_factory<Factory, Param, RouteImpl: Fn() -> String + 'static>(&mut self, path: &'static str, route_factory: Factory)
where
  Factory: ServiceFnOnce<Param, DDIResult<RouteImpl>> + 'static {
  self.service_factory_var(path, move |provider: &ServiceProvider| {
    Ok(Service::new(route_factory.run_with_once(provider)??) as Route)
  })
}

}

impl HttpCollectionExt for T {}

// -------------- usage ---------------

let mut services = ServiceCollection::new();

services.installroute("/index", || "".tostring()); services.installroute("/404", || "404".tostring()); services.installroutefactory("/business", |business: &Service| { let ownedbusiness = business.clone(); Ok(move || ownedbusiness.value.clone()) }); services.install_http();

services.service(Service::new(BusinessService { value: "hello".to_string() }));

let provider = services.provider(); asserteq!(provider.get::().unwrap().routes.get("/index").unwrap()(), ""); asserteq!(provider.get::().unwrap().routes.get("/404").unwrap()(), "404"); assert_eq!(provider.get::().unwrap().routes.get("/business").unwrap()(), "hello"); ```

License

This project is licensed under The MIT License.

Credits

Inspired by Dependency injection in .NET.