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日 |
#1175 在 Rust模式
45KB
893 行
DDI(动态依赖注入)
此库提供了一种通用的依赖注入容器,可以轻松集成到任何应用程序中,并可以通过扩展特性进行极度扩展。
依赖注入是一种常见的设计模式,主要用在一些框架中,如Rocket、Actix Web、bevy。使用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
我们建议将所有服务包装在 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_var
或 service_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_var
和 service_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 中的依赖注入 启发。