5个版本

0.2.1 2023年3月2日
0.2.0 2023年3月2日
0.1.2 2023年2月15日
0.1.1 2023年2月14日
0.1.0 2023年2月14日

#1175Rust模式

MIT 许可证

45KB
893

DDI(动态依赖注入)

此库提供了一种通用的依赖注入容器,可以轻松集成到任何应用程序中,并可以通过扩展特性进行极度扩展。

依赖注入是一种常见的设计模式,主要用在一些框架中,如RocketActix Webbevy。使用ddi,您可以在没有此类框架的情况下实现依赖注入,并可以构建自己的框架。

示例

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");

std特性

ddi默认支持no-std,如果启用std特性,内部数据结构将从alloc::collections::BTreeMap更改为std::collections::HashMap,并为DDIError实现std::error::Error。这将带来一点性能提升和可用性。

sync特性

如果启用sync特性,ddi将支持多线程,您可以在多个线程之间共享ServiceProvider

! 启用sync可能会导致您的现有代码无法编译!这是因为启用sync需要ServiceCollection中的实例实现send + sync,以及ServiceFactory实现send。默认情况下没有这样的限制。

基本用法

首先,您需要在 ServiceCollection 中注册所有服务,这是一个包含所有服务的容器,ServiceCollection 存储了一系列三元组(类型、名称、实现)。您可以使用 ServiceCollection::service 将项目添加到其中。

例如,以下代码将向 ServiceCollection 添加一个项目 (&str, "default", "helloworld")

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

在这里,服务的“实现”也可以是一个函数,是服务的工厂。工厂函数是惰性执行的,只有在使用服务时才会执行。例如。

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

服务工厂可以使用参数获取其他服务的依赖项。`ddi` 将根据参数的类型传递相应的服务。由于 Rust 的引用规则,参数的类型必须是不可变引用类型。

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

注册所有服务后,使用 [ServiceCollection::provider()] 获取 ServiceProvider,然后您可以从 ServiceProvider 获取任何您想要的服务。

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

设计模式

* 使用 Service<T> (Arc) 包装您的服务

当服务想要持有其他服务的引用时,应将引用的服务包装在 Arc<T> 中以正确处理生命周期。`ddi` 定义了一个别名 `type Service = Arc` 用于这种模式。

我们建议将所有服务包装在 Service<T> 中,以使服务之间的交叉引用更容易。

这不允许循环引用,因为 `ddi` 不允许循环依赖,这会导致 `DDIError::CircularDependencyDetected` 错误。

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());

* 使用扩展特质来扩展 ServiceCollection

扩展特质使 ServiceCollection 非常易于扩展。以下示例显示了使用扩展特质将多个服务注册到单个函数中的用法。

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());

* 在工厂中使用 ServiceProvider 获取其他服务的动态依赖项

在之前的示例中,服务工厂使用静态参数获取依赖项,在以下示例中,我们使用 ServiceProvider 动态获取依赖项。

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(
  |provider: &ServiceProvider| {
    if let Ok(hardware) = provider.get::<Service<HardwareDecoder>>() {
      Ok(Playback { decoder: hardware.clone() })
    } else {
      Ok(Playback { decoder: provider.get::<Service<SoftwareDecoder>>()?.clone() })
    }
  }
);

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

* 使用 `service_var` 或 `service_factory_var` 注册服务的变体

可以通过使用 service_varservice_factory_var 来注册同一种类型服务的多个变体,ServiceCollection。注册变体时,需要为每个变体声明 ServiceName,默认的 ServiceName 是 "default",它是通过 service 或 service_factory 函数注册的。

以下示例演示了如何基于服务变体构建 http 服务器。

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

use ddi::*;

type Route = Service<dyn Fn() -> String>;
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(
  |provider: &ServiceProvider| {
    let routes = provider.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");

使用扩展特性简化服务变体的注册

在上一个示例中,我们使用了 service_varservice_factory_var 来注册 http 服务器的路由,但代码难以理解且没有类型检查。以下示例展示了如何使用扩展特性简化路由定义并解决这些问题。

use ddi::*;

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

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

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

    fn install_route<Param>(&mut self, path: &'static str, route: impl ServiceFn<Param, String> + 'static) {
      self.service_factory_var(path, move |provider: &ServiceProvider| {
        let owned_provider = provider.clone();
        Ok(Service::new(move || route.run_with(&owned_provider).expect("123")) 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("/business", |business: &BusinessService| business.value.to_string());
services.install_http();

services.service(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");

示例中的 install_route 函数使用 ServiceFn 特性作为参数,这是一个强大的类型,使用 ServiceFn::run_with 函数来自动从 ServiceProvider 中提取 Fn 参数并执行它。

许可证

本项目受 MIT 许可证 授权。

致谢

.NET 中的依赖注入 启发。

无运行时依赖

特性