依赖注入模式

rust-dix inject 模式(推荐)

#[derive(Inject)]
pub struct LoginHandler {
    auth: Arc<dyn IAuthService>,
}

#[handler(inject)]
#[async_trait]
impl IRequestHandler<LoginRequest, AuthResponse> for LoginHandler { ... }

`#[derive(Inject)]` + `#[handler(inject)]` 自动:

1. 注册 Handler 为 singleton 2. 解析字段依赖(如 `Arc<dyn IAuthService>`) 3. 注册为 `dyn IRequestHandler<LoginRequest, AuthResponse>`

业务服务:接口在 contracts,实现在 handlers

Handler 和 `IHostedService` **只依赖 trait**,不依赖具体实现类型。

// contracts/docs.rs — 接口与 DTO(仅依赖框架)
pub trait IDocumentService: Send + Sync {
    fn index(&self, work: &str) -> Result<DocIndex, String>;
    fn content(&self, work: &str, path: &str) -> Result<DocContent, String>;
}

// handlers/docs.rs — 实现 + 自动注册
#[inject]
pub struct DocService {
    paths: Arc<AppPaths>,
}

impl IDocumentService for DocService { ... }

// handlers/docs.rs — Handler 消费接口
#[inject]
pub struct GetDocIndexHandler {
    docs: Arc<dyn IDocumentService>,
}

要点:

| 层级 | 位置 | 依赖类型 | 注册方式 | |------|------|---------|---------| | 接口 trait | `contracts/` | — | 无注册 | | Service 实现 | `handlers/` | `Arc<AppPaths>` 等 | `#[inject]` | | Handler | `handlers/` | `Arc<dyn IDocumentService>` | `#[derive(Inject)]` + `#[handler(inject)]` |

新增业务能力时:**先在 contracts 写 trait + DTO → 在 handlers 写实现并加 `#[inject]` → Handler 注入 `Arc<dyn I…Service>`**,无需改 `main.rs`。

组合根:只注册框架外类型

`main.rs` 仅配置 Host;`bootstrap::configure` 只注册 rust-dix 无法自行构造的类型:

Host::builder()
    .register(common::bootstrap::configure)  // AppPaths + DbContext
    .use_spa(...)
    .add_authentication()
    .build();
pub fn configure(mut svc: ServiceCollection) -> ServiceCollection {
    let paths = AppPaths::resolve();
    svc = svc.singleton::<AppPaths>(move |_| Arc::new(paths));
    svc = svc.singleton::<Mutex<DbContext>>(move |_| { ... });
    svc
}

业务 Service(`DocService`、`BlogService`)**不在此手动注册**——由 `#[inject]` 在 handlers 中自动收集。

DbContext(rust-ef)

let mut builder = DbContextOptionsBuilder::new();
builder
    .use_sqlite(db_path)
    .add_interceptor(AuditInterceptor);

`DbContext` 与 `DbContextOptions` 属于 ORM 基础设施,在 `bootstrap::configure` 注册;实体与迁移在 `domain/`,拦截器在 `common/`。

服务间依赖链

GetDocIndexHandler (handlers/)
    → Arc<dyn IDocumentService>  (DocService in handlers/)
        → Arc<AppPaths>          (bootstrap)
        → domain entities

依赖在编译期由字段类型声明,运行时由 DI 容器解析。

反模式

| ❌ 反模式 | ✅ 替代 | |---------|--------| | `I…Service` trait 放在 `services/` 或 `handlers/` 私有模块 | trait 定义在 `contracts/` | | Handler 依赖 `Arc<DocService>` 具体类型 | `Arc<dyn IDocumentService>` | | 在 `main.rs` 逐个 `singleton::<BlogService>` | handlers 中 `#[inject]` 实现 `IBlogService` | | `contracts` 引用 `domain` 类型作为 Response | DTO 定义在 contracts,handlers 中映射 | | Handler 内 `UserRepository::new()` | DI 注入 `Arc<dyn I…Service>` | | Handler 间直接调用 | `IMediator::send()` |

小结

生产项目统一使用 **`contracts` 定义接口 + `handlers` 实现 + `#[inject]` + `#[handler(inject)]`**,`main.rs` 只做 Host 与基础设施配置。

下一节:[IHostedService 后台服务](hosted-services.md)