Dynamic dependency injection library for rust.

Related tags

Command-line ddi
Overview

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

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::<TestService>().unwrap().0, "1helloworld");

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]

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.

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.

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].

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.

use ddi::*;

struct Bar;
struct Foo(Service<Bar>);

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

let provider = services.provider();
assert!(provider.get::<Service<Foo>>().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.

use ddi::*;

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

#[derive(Clone)]
struct DbConfiguration;
struct DbService(DbConfiguration, Service<DbConnectionManager>);
struct DbConnectionManager;

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

impl<T: ServiceCollectionExt> DbServiceCollectionExt for T {}

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

let mut services = ServiceCollection::new();

services.install_database();

let provider = services.provider();
assert!(provider.get::<Service<DbService>>().is_ok());

* Use [ServiceRef] 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 [ServiceRef] to get the dependencies dynamically.

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<dyn Decoder>
}

const SUPPORT_HARDWARE_DECODER: bool = false;

let mut services = ServiceCollection::new();

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

let provider = services.provider();
assert_eq!(provider.get::<Playback>().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.

use ddi::*;

type Route = Service<dyn Fn() -> String + Send + Sync>;
struct HttpService {
  routes: std::collections::HashMap<String, Route>
}
struct BusinessService {
  value: String
}

let mut services = ServiceCollection::new();

services.service_var("/index", Service::new(|| "<html>".to_string()) as Route);
services.service_var("/404", Service::new(|| "404".to_string()) as Route);
services.service_factory_var(
  "/business",
  |business: &Service<BusinessService>| {
    let owned_business = business.clone();
    Ok(Service::new(move || owned_business.value.clone()) as Route)
  }
);
services.service_factory(
  |service_ref: &ServiceRef| {
    let routes = service_ref.get_all::<Route>()?
      .into_iter()
      .map(|(path, route)| (path.to_string(), route.clone()))
      .collect();
    Ok(HttpService { routes })
  }
);
services.service(Service::new(BusinessService {
  value: "hello".to_string()
}));

let provider = services.provider();
assert_eq!(provider.get::<HttpService>().unwrap().routes.get("/index").unwrap()(), "<html>");
assert_eq!(provider.get::<HttpService>().unwrap().routes.get("/404").unwrap()(), "404");
assert_eq!(provider.get::<HttpService>().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.

use ddi::*;

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

type Route = Service<dyn Fn() -> String + Send + Sync>;
struct HttpService {
  routes: std::collections::HashMap<String, Route>
}
struct BusinessService {
  value: String
}

pub trait HttpCollectionExt: ServiceCollectionExt {
    fn install_http(&mut self) {
      self.service_factory(
        |service_ref: &ServiceRef| {
          let routes = service_ref.get_all::<Route>()?
            .into_iter()
            .map(|(path, route)| (path.to_string(), route.clone()))
            .collect();
          Ok(HttpService { routes })
        }
      );
    }

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

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

impl<T: ServiceCollectionExt> HttpCollectionExt for T {}

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

let mut services = ServiceCollection::new();

services.install_route("/index", || "<html>".to_string());
services.install_route("/404", || "404".to_string());
services.install_route_factory("/business", |business: &Service<BusinessService>| {
  let owned_business = business.clone();
  Ok(move || owned_business.value.clone())
});
services.install_http();

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

let provider = services.provider();
assert_eq!(provider.get::<HttpService>().unwrap().routes.get("/index").unwrap()(), "<html>");
assert_eq!(provider.get::<HttpService>().unwrap().routes.get("/404").unwrap()(), "404");
assert_eq!(provider.get::<HttpService>().unwrap().routes.get("/business").unwrap()(), "hello");

License

This project is licensed under The MIT License.

Credits

Inspired by Dependency injection in .NET.

You might also like...
An embeddable dynamic programming language for Rust.

rune Visit the site 🌐 - Read the book 📖 An embeddable dynamic programming language for Rust. Contributing If you want to help out, there should be a

Like a cell, but make lifetimes dynamic instead of ownership

LendingCell is a mutable container that allows you to get an owned reference to the same object. When the owned reference is dropped, ownership return

Apple dynamic HEIF wallpapers on GNU/Linux.
Apple dynamic HEIF wallpapers on GNU/Linux.

timewall Apple dynamic HEIF wallpapers on GNU/Linux. Features: Support for original HEIF/HEIC dynamic wallpaper files used in MacOS. Support for all s

AI-powered game engine for dynamic, personalized experiences in evolving worlds. Ethical, accessible, inclusive.

ARCADIA: Advanced and Responsive Computational Architecture for Dynamic Interactive Ai: A Whitepaper By Reuven Cohen (rUv) Introduction Imagine a futu

Inspect dynamic dependencies of Mach-O binaries recursively

dylibtree dylibtree is a tool for inspecting the dynamic dependencies of a Mach-O binary recursively. It can be useful to understand what library load

A versatile and dynamic music bot designed to elevate the musical experience within Discord servers.

Masayoshi Masayoshi is a discord music bot written in Rust for making a great experience within Discord servers with support to Youtube, SoundCloud, S

Rust Imaging Library's Python binding: A performant and high-level image processing library for Python written in Rust

ril-py Rust Imaging Library for Python: Python bindings for ril, a performant and high-level image processing library written in Rust. What's this? Th

This library provides a convenient derive macro for the standard library's std::error::Error trait.

derive(Error) This library provides a convenient derive macro for the standard library's std::error::Error trait. [dependencies] therror = "1.0" Compi

A readline-like library in Rust.

liner A Rust library offering readline-like functionality. CONTRIBUTING.md Featues Autosuggestions Emacs and Vi keybindings Multi-line editing History

Owner
EYHN
Tell your world! https://t.me/EEEEYHN
EYHN
Rusty Shellcode Reflective DLL Injection (sRDI) - A small reflective loader in Rust 4KB in size for generating position-independent code (PIC) in Rust.

Shellcode Reflective DLL Injection (sRDI) Shellcode reflective DLL injection (sRDI) is a process injection technique that allows us to convert a given

null 242 Jul 5, 2023
Process Injection via Component Object Model (COM) IRundown::DoCallback().

COM PROCESS INJECTION for RUST Process Injection via Component Object Model (COM) IRundown::DoCallback(). 该技术由 @modexpblog 挖掘发现,在我对该技术进行深入研究过程中,将原项目 m

Lane 7 Jan 20, 2023
zero-dependency 3d math library in rust

dualquat A lightweight, zero-dependency 3d math library for use in Dual Quaternion based physics simulation. Capable of representing and transforming

null 4 Nov 11, 2022
A lightweight, zero-dependency struct diffing library which allows changed fields to be collected and applied

structdiff A lightweight, zero-dependency struct diffing library which allows changed fields to be collected and applied. Derive Difference on a struc

null 7 Dec 25, 2022
A high-performance WebSocket integration library for streaming public market data. Used as a key dependency of the `barter-rs` project.

Barter-Data A high-performance WebSocket integration library for streaming public market data from leading cryptocurrency exchanges - batteries includ

Barter 23 Feb 3, 2023
python dependency vulnerability scanner, written in Rust.

?? Pyscan A dependency vulnerability scanner for your python projects, straight from the terminal. ?? blazingly fast scanner that can be used within l

Aswin. 80 Jun 4, 2023
A zero-dependency crate for writing repetitive code easier and faster.

akin A zero-dependency crate for writing repetitive code easier and faster. Check Syntax for information about how to use it. Why? Example Syntax NONE

LyonSyonII 36 Dec 29, 2022
A truly zero-dependency crate providing quick, easy, reliable, and scalable access to the name "jordin"

jordin Finally! A truly zero-dependency crate providing quick, easy, reliable, and scalable access to the name "jordin". Additionally, this one-of-a-k

jordin 2 Aug 4, 2022
Track and query Cargo dependency graphs.

cargo-guppy: track and query dependency graphs This repository contains the source code for: guppy: a library for performing queries on Cargo dependen

guppy 42 Dec 30, 2022
Generate a dependency list to thank them on README.

thanks-dependencies This generates list of dependencies. I think it's better to publish dependencies explicitly on documentation. Of course users can

keiya sasaki 7 Jan 30, 2023