5 个版本
0.1.4 | 2021 年 6 月 4 日 |
---|---|
0.1.3 | 2020 年 7 月 2 日 |
0.1.2 | 2020 年 7 月 1 日 |
0.1.1 | 2020 年 7 月 1 日 |
0.1.0 | 2020 年 6 月 27 日 |
#3 在 #io-c
每月 37 次下载
用于 sai
9KB
185 行
Sai
Sai 是一个用于管理软件组件生命周期和依赖的框架。在某些语言中,它被称为 "IoC" 和 "依赖注入"。该框架的主要应用场景是中等/大型规模的网络服务。
Sai 生态系统由两个主要概念组成: System,Component。System 是一个运行时单元,它控制所有 Component 的生命周期。Component 是一组逻辑。Component 可以依赖于其他 Component,也可以有自己的内部状态。
特性
- ✅ 为 Async Rust 构建
- ✅ 最小化样板代码
- ✅ 在稳定版上运行
入门
让我们用 Sai 来了解一下基本用法。
步骤 1:定义你的组件
在 Sai 中定义组件就像定义一个结构体一样简单。使用 #[derive(Component)]
注释结构体,就可以将其转换为组件定义。
use sai::{Component};
#[derive(Component)]
pub struct FooController {}
impl FooController {
pub fn do_something (&self) {
// Some logic
}
}
#[derive(Component)]
pub struct DbPool {}
步骤 2:使用 #[injected]
声明依赖
Components 将自然地相互依赖以使事情工作。在上面的例子中,假设 FooController
想要访问 DbPool
,我们只需要将 DbPool
作为字段添加到 FooController
,并使用 #[injected]
注释它,并用 Injected
包装它。稍后我们将介绍的 System
将为您智能地准备依赖。
use sai::{Component, Injected};
#[derive(Component)]
pub struct FooController {
#[injected]
pool: Injected<DbPool>
}
impl FooController {
pub fn do_something (&self) {
// self.pool is accessible here.
}
}
// the rest is the same
你可能想知道 DbPool
组件的所有权会发生什么变化?在 Sai 中,System
控制所有组件的生命周期。Injected
结构体实际上是 Arc
的包装。
步骤 3(可选):使用 #[lifecycle]
控制组件的生命周期
组件拥有显式启动逻辑非常常见,例如:初始化数据库连接、绑定网络端口、连接到消息队列等。
在 Sai 中,要控制组件的生命周期,只需用 #[lifecycle]
注释组件,并为其实现 ComponentLifecycle
。
use sai::{Component, Injected, ComponentLifecycle, async_trait};
// ... FooController is untouched
// Assuming Pool is a connection pool type
#[derive(Component)]
#[lifecycle] // < --- NOTE HERE HERE
pub struct DbPool {
pool: Option<Pool>
}
#[async_trait]
impl ComponentLifecycle for DbPool {
async fn start(&mut self) {
println("Starting up DB connection pool...");
// Just an example
self.pool = Some(Pool::new(/*...*/))
}
async fn stop(&mut self) {
println("Shutting down DB connection pool...");
// You don't have to do much here:
// when System stops a Component, it will drop it as soon as possible.
// But it's still good to ensure component shutdown cleanly instead of relying on Drop,
// though it's not always possible.
}
}
一些注意事项
async_trait
对于实现ComponentLifecycle
是必要的。它从这个库中重新导出:此库。- 对于 Sai 没有注入的字段,它们必须实现
Default
,否则无法编译。
步骤 4:创建一个系统(使用组件)+ 启动系统
一旦我们定义了一些组件,我们只需将它们组合成一个系统。系统是一个状态机,其中包含了一组组件。在 Sai 中,这组组件由 component registry
表示。
use sai::{component_registry, System, /* other stuff... */};
use tokio::signal;
/* FooController + DbPool defined as above */
/* Define a component registry called RootRegistry which has two components */
component_registry!(RootRegistry, [ FooController, DbPool ]);
#[tokio::main] // Or async-std
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// This is the key, we define a system using the RootRegistry.
let mut system : System<RootRegistry> = System::new();
println!("System starting up...");
system.start().await;
println!("System started.");
// Waiting for Ctrl-c
signal::ctrl_c().await?;
println!("System shutting down...");
system.stop().await;
println!("System shutted down.");
Ok(())
}
系统将负责所有组件的生命周期。在 start
中,系统将逐个创建和启动所有 已注册 的组件,并按照它们的依赖关系进行连接(见步骤 2)。在 stop
中,系统将逐个停止并 删除 系统中的所有组件,其顺序与 start
中的顺序相反。
在大型系统中,通常会将多个注册表组合成一个,每个注册表可以代表系统的一个模块。Sai 提供了一个实用宏 combine_component_registry!
用于此目的。
combine_component_registry!(RootRegistry, [
ApiRegistry,
WebRegistry,
BusinessLogicRegistry,
// Any number of registries
])
🎉🎉 你已经毕业了!
感谢你阅读这份指南。Sai 是一个最小化的库。尽管这被称为“基本”指南,但它已经涵盖了该库的大部分内容。我希望 Sai 能帮到你。
常见问题解答(FAQs)
-
Q:Sai 是什么意思?
- 实际上没什么。碰巧是我的猫的名字。由于 cargo 只有单个命名空间,许多好名字已经被预留了(是的,它们被预留了而不是使用)。
-
Q:为什么我需要这个库?
- 通过多层的函数传递常见依赖项既繁琐又容易出错。
- 在中型/大型网络服务中,对启动/关闭逻辑进行细粒度控制非常重要。没有好的框架,很难完成像以下这些事情:
- 从密钥管理器获取所有机密
- 然后启动数据库/Redis 连接
- 然后监听 x 端口进行流量处理
- 然后启动一个新服务器进行健康检查
- 最后,以相反的顺序关闭上述所有内容
-
Q:这个库能处理循环依赖吗?
- 目前不能。
-
Q:我能对单个组件进行单元测试吗?
- 是的,神奇的 mockall 会帮到你。你还可以从示例中的单元测试中学习。
-
Q:有什么限制吗?
- 目前,很难找到具有完美/细粒度控制关闭的 Async Rust 库。
- 此库中的错误处理/报告还不够完美。(进行中)
- 目前无法处理循环组件依赖。(PR 欢迎提交)
相关项目
- Component(Clojure)
- InversifyJS(JavaScript/TypeScript)
依赖项
~1.5MB
~35K SLoC