#component #system #ioc #di

sai

针对 Rust 的 IoC/DI 框架

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 网页编程

MIT/Apache

28KB
539

Sai


Sai 是一个用于管理软件组件生命周期和依赖的框架。在某些语言中,它被称为“IoC”和“依赖注入”。该框架的主要用例是中等/大型网络服务。

Sai 生态系统包括两个主要概念: SystemComponent。一个 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)

依赖项

~1.5MB
~35K SLoC