#component #system #ioc #di

sai_component_derive

Sai 的 #[derive(Component)] 实现

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

MIT/Apache

9KB
185

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] 声明依赖

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 欢迎提交)

依赖项

~1.5MB
~35K SLoC