38 个版本
0.19.3 | 2023 年 12 月 6 日 |
---|---|
0.19.1 | 2023 年 4 月 14 日 |
0.18.5 | 2023 年 3 月 22 日 |
0.18.2 | 2022 年 2 月 7 日 |
0.1.0 | 2019 年 7 月 30 日 |
#420 在 异步
151 每月下载
53KB
505 行
async-injector
异步依赖注入的 Rust 库。
该库提供了粘合剂,允许构建健壮的解耦应用程序,在运行时可以动态重新配置。
有关实际使用示例,请参阅OxidizeBot
,它是为此编写的。
用法
将 async-injector
添加到您的 Cargo.toml
文件中。
[dependencies]
async-injector = "0.19.3"
示例
以下我们将展示如何注入一个 模拟 的 Database
。这里的想法是,如果数据库连接的某些内容发生变化,将创建一个新的 Database
实例,并导致应用程序重新配置自己。
use async_injector::{Key, Injector, Provider};
#[derive(Debug, Clone)]
struct Database;
#[derive(Debug, Provider)]
struct Service {
#[dependency]
database: Database,
}
async fn service(injector: Injector) -> Result<(), Box<dyn std::error::Error>> {
let mut provider = Service::provider(&injector).await?;
let Service { database } = provider.wait().await;
println!("Service got initial database {database:?}!");
let Service { database } = provider.wait().await;
println!("Service got new database {database:?}!");
Ok(())
}
注意: 这作为
database
示例提供。cargo run --example database
上面的 Injector
提供了一个结构化的广播系统,允许将配置更新干净地集成到异步上下文中。更新本身由负责构造 Database
实例的其他组件触发。
以这种方式构建应用程序的组件意味着可以重新配置它而无需重新启动它,从而提供更丰富的用户体验。
注入多个相同类型的东西
在前一节中,您可能已经注意到注入的值仅由其类型 Database
进行区分。在本例中,我们将展示如何使用 Key
将不同名称的值标记为同一类型以区分它们。这在与过于通用的类型(如 String
)打交道时非常有用。
必须使用支持序列化的标签,使用serde
。它还必须不使用任何不可哈希的组件,例如f32
和f64
。
一个简单的问候程序
以下示例展示了如何使用Key
将两个不同的值注入到异步greeter
中。
use async_injector::{Key, Injector};
async fn greeter(injector: Injector) -> Result<(), Box<dyn std::error::Error>> {
let name = Key::<String>::tagged("name")?;
let fun = Key::<String>::tagged("fun")?;
let (mut name_stream, mut name) = injector.stream_key(name).await;
let (mut fun_stream, mut fun) = injector.stream_key(fun).await;
loop {
tokio::select! {
update = name_stream.recv() => {
name = update;
}
update = fun_stream.recv() => {
fun = update;
}
}
let (Some(name), Some(fun)) = (&name, &fun) else {
continue;
};
println!("Hi {name}! I see you do \"{fun}\" for fun!");
return Ok(());
}
}
注意:可以使用以下方法运行此代码
cargo run --example greeter
上述循环可以使用Provider
derive更简单地实现,因此让我们这样做。
use async_injector::{Injector, Provider};
#[derive(Provider)]
struct Dependencies {
#[dependency(tag = "name")]
name: String,
#[dependency(tag = "fun")]
fun: String,
}
async fn greeter(injector: Injector) -> Result<(), Box<dyn std::error::Error>> {
let mut provider = Dependencies::provider(&injector).await?;
let Dependencies { name, fun } = provider.wait().await;
println!("Hi {name}! I see you do \"{fun}\" for fun!");
Ok(())
}
注意:可以使用以下方法运行此代码
cargo run --example greeter_provider
Provider
derive
Provider
derive可以方便地实现等待一组特定依赖项变得可用的必要机制。
它构建了一个名为<name>Provider
的伴随结构,它进而实现了以下一组方法
use async_injector::{Error, Injector};
impl Dependencies {
/// Construct a new provider.
async fn provider(injector: &Injector) -> Result<DependenciesProvider, Error>
}
struct DependenciesProvider {
/* private fields */
}
impl DependenciesProvider {
/// Try to construct the current value. Returns [None] unless all
/// required dependencies are available.
fn build(&mut self) -> Option<Dependencies>
/// Wait until we can successfully build the complete provided
/// value.
async fn wait(&mut self) -> Dependencies
/// Wait until the provided value has changed. Either some
/// dependencies are no longer available at which it returns `None`,
/// or all dependencies are available after which we return the
/// build value.
async fn wait_for_update(&mut self) -> Option<Dependencies>
}
Provider
的固定参数
任何没有#[dependency]
属性的参数称为“固定”参数。这些参数必须在调用provider
构造函数时传递。它们也可以在标签构建过程中使用。
use async_injector::{Injector, Key, Provider};
#[derive(Provider)]
struct Dependencies {
name_tag: &'static str,
#[dependency(tag = name_tag)]
name: String,
}
async fn greeter(injector: Injector) -> Result<(), Box<dyn std::error::Error>> {
let mut provider = Dependencies::provider(&injector, "name").await?;
let Dependencies { name, .. } = provider.wait().await;
println!("Hi {name}!");
Ok(())
}
依赖项
~4–5.5MB
~91K SLoC