{"id":13632527,"url":"https://github.com/zhming0/sai","last_synced_at":"2025-05-03T17:33:43.307Z","repository":{"id":50101430,"uuid":"272141698","full_name":"zhming0/sai","owner":"zhming0","description":"A minimal IoC/DI framework for Rust.","archived":false,"fork":false,"pushed_at":"2021-06-04T05:51:01.000Z","size":111,"stargazers_count":69,"open_issues_count":0,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-29T03:44:05.277Z","etag":null,"topics":["async","dependency-injection","inversion-of-control","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zhming0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-14T05:51:10.000Z","updated_at":"2024-12-18T11:49:33.000Z","dependencies_parsed_at":"2022-09-02T14:22:16.348Z","dependency_job_id":null,"html_url":"https://github.com/zhming0/sai","commit_stats":null,"previous_names":["zhming0/shine"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhming0%2Fsai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhming0%2Fsai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhming0%2Fsai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhming0%2Fsai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zhming0","download_url":"https://codeload.github.com/zhming0/sai/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252227153,"owners_count":21714953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","dependency-injection","inversion-of-control","rust"],"created_at":"2024-08-01T22:03:05.861Z","updated_at":"2025-05-03T17:33:42.994Z","avatar_url":"https://github.com/zhming0.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eSai\u003c/h1\u003e\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003c!-- Crates version --\u003e\n  \u003ca href=\"https://crates.io/crates/sai\"\u003e\n    \u003cimg src=\"https://img.shields.io/crates/v/sai.svg?style=flat-square\"\n    alt=\"Crates.io version\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Downloads --\u003e\n  \u003ca href=\"https://crates.io/crates/sai\"\u003e\n    \u003cimg src=\"https://img.shields.io/crates/d/sai.svg?style=flat-square\"\n      alt=\"Download\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- docs.rs docs --\u003e\n  \u003ca href=\"https://docs.rs/sai\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" /\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch3\u003e\n    \u003ca href=\"https://docs.rs/sai\"\u003e\n      API Docs\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"examples\"\u003e\n      Examples\n    \u003c/a\u003e\n  \u003c/h3\u003e\n\u003c/div\u003e\n\nSai is a framework for managing lifecycle and dependency of your software components.\nIn some languages, it was called \"IoC\" and \"Dependency Injection\".\nThe main usecase of this framework is on medium/large scale web services.\n\nThe Sai ecosystem consists of two major concepts: [System](struct.System.html), [Component](trait.Component.html).\nA System is a runtime unit that control lifecycles of all Components.\nA Component is a group of logic. A Component can depends on other Components and it can\nalso have its own internal state.\n\n## Features\n- ✅ Build for Async Rust\n- ✅ Minimal boilerplate\n- ✅ Runs in stable\n\n## Get Started\n\nLet's go through the basic usage with Sai.\n\n### Step 1: Define your component\n\nDefining a component in Sai is as simple as defining a struct.\nAnnotate the struct with `#[derive(Component)]` will turn this struct to a component definition.\n\n```rust\nuse sai::{Component};\n\n#[derive(Component)]\npub struct FooController {}\n\nimpl FooController {\n    pub fn do_something (\u0026self) {\n        // Some logic\n    }\n}\n\n#[derive(Component)]\npub struct DbPool {}\n\n```\n\n### Step 2: Declare dependencies using `#[injected]`\n\nComponents will naturally depend on each other to make things work.\nIn above example, let's say `FooController` wants to access `DbPool`,\nall we need to do is to add `DbPool` as a field to `FooController` + annotate it using `#[injected]` + wrapped it with `Injected`.\nThe `System`, which we will go through later, will smartly prepare dependencies for you.\n\n```rust\nuse sai::{Component, Injected};\n\n#[derive(Component)]\npub struct FooController {\n    #[injected]\n    pool: Injected\u003cDbPool\u003e\n}\n\nimpl FooController {\n    pub fn do_something (\u0026self) {\n        // self.pool is accessible here.\n    }\n}\n\n// the rest is the same\n```\n\nYou may wonder what happens to the ownership of `DbPool` component?\nIn Sai, the `System` controls the lifecycle of all components.\nThe `Injected` struct is basically a wrapper over `Arc`.\n\n### Step 3 (Optional): Control the lifecycle of your component with `#[lifecycle]`\n\nIt's very common for a component have explict startup logic,\ne.g. initiate DB connection, bind port for web traffic, connect to message queue, etc.\n\nIn Sai, to control the lifecycle of Component, simple annotate your component with `#[lifecycle]` and implement `ComponentLifecycle` for it.\n```rust\nuse sai::{Component, Injected, ComponentLifecycle, async_trait};\n\n// ... FooController is untouched\n\n// Assuming Pool is a connection pool type\n#[derive(Component)]\n#[lifecycle]  // \u003c --- NOTE HERE HERE\npub struct DbPool {\n    pool: Option\u003cPool\u003e\n}\n\n#[async_trait]\nimpl ComponentLifecycle for DbPool {\n    async fn start(\u0026mut self) {\n        println(\"Starting up DB connection pool...\");\n        // Just an example\n        self.pool = Some(Pool::new(/*...*/))\n    }\n    async fn stop(\u0026mut self) {\n        println(\"Shutting down DB connection pool...\");\n\n        // You don't have to do much here:\n        // when System stops a Component, it will drop it as soon as possible.\n        // But it's still good to ensure component shutdown cleanly instead of relying on Drop,\n        // though it's not always possible.\n    }\n}\n```\n\nSome notes:\n- `async_trait` is necessary for implementing `ComponentLifecycle`. It's re-exported from [this library](https://github.com/dtolnay/async-trait).\n- For fields that are not injected by Sai, they have to implement `Default` otherwise it won't compile.\n\n### Step 4: Create a System using components + kickstart the System\n\nOnce we have defined a few components,\nWe just need to compose them into a System.\nA System is a state machine that contains a collection of components.\nThe collection of components is represented by `component registry` in Sai.\n\n```rust\nuse sai::{component_registry, System, /* other stuff... */};\nuse tokio::signal;\n\n/* FooController + DbPool defined as above */\n\n/* Define a component registry called RootRegistry which has two components */\ncomponent_registry!(RootRegistry, [ FooController, DbPool ]);\n\n#[tokio::main] // Or async-std\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n\n    // This is the key, we define a system using the RootRegistry.\n    let mut system : System\u003cRootRegistry\u003e = System::new();\n\n    println!(\"System starting up...\");\n    system.start().await;\n    println!(\"System started.\");\n\n    // Waiting for Ctrl-c\n    signal::ctrl_c().await?;\n\n    println!(\"System shutting down...\");\n    system.stop().await;\n    println!(\"System shutted down.\");\n    Ok(())\n}\n\n```\n\nThe System will in charge of lifecycles of all components.\nIn `start`, the system will create and start all **registered** components one-by-one and wire them up according to their dependencies (see step 2).\nIn `stop`, the system will stop and **drop** all components in system one-by-one in the reverse order of `start`.\n\nIn large system, it's common to compose multiple registries into one, each registry can represent a module of the system.\nSai provided a utility macro `combine_component_registry!` for it:\n\n```rust\ncombine_component_registry!(RootRegistry, [\n    ApiRegistry,\n    WebRegistry,\n    BusinessLogicRegistry,\n    // Any number of registries\n])\n```\n\n### 🎉🎉 You graduated!\nThanks for going over this guide.\nSai is a minimal library.\nAlthough this is called a \"basic\" guide, it already covers most of contents of this library.\nI hope Sai can help you.\n\n## FAQs\n\n- Q: What does \"Sai\" mean?\n  - Nothing really. It happens to be my cat's name. I can't find a good enough name because cargo has only single namespace and many good names are reserved (yes, they are reserved rather than used).\n\n- Q: Why do I need this library?\n  - It's tedius and error prone to pass common dependencies via multple layers of functions.\n  - In a medium/large sized web service, it's important to have a granular control over startup / shutdown logic. Without a good framework, it's difficult to do things like:\n    - Get all secrets from secret manager\n    - Then start DB/Redis connection\n    - Then start listening x port for traffic\n    - Then start a new server for healch check\n    - In the end, shutdown all above in the reverse order\n\n- Q: Does this handle circular dependency?\n  - No, it does not currently.\n\n- Q: Can I unit test a single component?\n  - Yes, the awesome [mockall](https://github.com/asomers/mockall) will help you get there. You can learn from unit tests in examples too.\n\n- Q: Is any there limitation?\n  - Currently, it's hard to find Async Rust libraries that has a perfect/granular control over shutdown.\n  - Error handling/reporting in this library isn't perfect. (WIP)\n  - Can't handle circular component dependency at this moment. (PR welcomed)\n\n## Related projects\n\n- [Component](https://github.com/stuartsierra/component) (Clojure)\n- [InversifyJS](https://github.com/inversify/InversifyJS) (Javascript/Typescript)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhming0%2Fsai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhming0%2Fsai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhming0%2Fsai/lists"}