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 日 |
#1069 in 网页编程
28KB
539 行
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]
声明依赖
组件会自然地相互依赖以使事物正常工作。在上面的例子中,假设 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.
}
}
一些注意事项
- 实现
ComponentLifecycle
需要使用async_trait
。它从这个库中导出。 - 对于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
中,系统将按相反顺序逐个停止并drop
系统中的所有组件。
在大系统中,通常会将多个注册表组合成一个,每个注册表可以表示系统的一个模块。Sai为此提供了实用宏combine_component_registry!
。
combine_component_registry!(RootRegistry, [
ApiRegistry,
WebRegistry,
BusinessLogicRegistry,
// Any number of registries
])
🎉🎉 恭喜你毕业了!
感谢您阅读这份指南。Sai是一个最小的库。尽管这只是一个“基本”指南,但它已经涵盖了该库的大部分内容。希望Sai能帮到您。
常见问题解答
-
问题:Sai是什么意思?
- 实际上没什么。它恰好是我的猫的名字。我找不到一个足够好的名字,因为cargo只有一个命名空间,很多好名字都被预留了(是的,它们被预留而不是使用)。
-
问题:为什么我需要这个库?
- 通过多层函数传递常用依赖项既麻烦又容易出错。
- 在一个中等/大型的网络服务中,对启动/关闭逻辑进行细粒度控制非常重要。没有好的框架,很难完成以下操作
- 从密钥管理器获取所有密钥
- 然后启动数据库/Redis连接
- 然后监听x端口进行流量
- 然后启动一个新服务器进行心跳检查
- 最后,按相反顺序关闭所有以上操作
-
问题:这个库处理循环依赖吗?
- 目前不行。
-
问题:我可以对单个组件进行单元测试吗?
- 是的,优秀的mockall将帮助您实现这一点。您也可以从示例中的单元测试中学习。
-
问题:有局限性吗?
- 目前,很难找到具有完美/细粒度控制关闭的Async Rust库。
- 这个库的错误处理/报告并不完美。(正在进行中)
- 目前无法处理循环组件依赖。(欢迎提交PR)
相关项目
- Component(Clojure)
- InversifyJS(JavaScript/TypeScript)
依赖项
~1.5MB
~35K SLoC