业务场景实战
本文档提供真实业务场景下的 rust-dix 完整使用方案,涵盖从项目初始化到上线部署的全流程。
> 本文示例采用简短路径风格,假定文件顶部已 `use rust_dix::*;`。导入约定详见 [用法指南](usage-guide.md#1-导入约定路径简化)。
---
场景 1:Web 应用完整架构(Axum / Actix-web)
项目结构
src/
├── main.rs # 组合根:构建容器、启动服务器
├── contracts/ # 接口层(trait + DTO)
│ ├── mod.rs
│ ├── user.rs # IUserService trait
│ └── order.rs # IOrderService trait
├── services/ # 业务实现层
│ ├── mod.rs
│ ├── user_service.rs
│ └── order_service.rs
├── repositories/ # 数据访问层
│ ├── mod.rs
│ ├── user_repo.rs
│ └── order_repo.rs
└── infrastructure/ # 基础设施
├── mod.rs
├── db.rs # DbPool
└── config.rs # AppConfig
合约层:contracts/user.rs
use std::sync::Arc;
pub trait IUserService: Send + Sync {
fn create(&self, name: &str, email: &str) -> Result<User, String>;
fn find(&self, id: u64) -> Result<User, String>;
}
pub struct User {
pub id: u64,
pub name: String,
pub email: String,
}
实现层:services/user_service.rs
use rust_dix::*;
use std::sync::Arc;
use crate::contracts::user::*;
use crate::repositories::user_repo::IUserRepo;
// struct 用 #[derive(Inject)] 仅生成构造函数(不注册),impl 用 #[inject] 注册为 trait
#[derive(Inject)]
pub struct UserService {
#[inject]
repo: Arc<dyn IUserRepo>,
}
#[inject(transient)]
impl IUserService for UserService {
fn create(&self, name: &str, email: &str) -> Result<User, String> {
self.repo.insert(name, email)
}
fn find(&self, id: u64) -> Result<User, String> {
self.repo.find_by_id(id)
}
}
组合根:main.rs
use rust_dix::*;
use std::sync::Arc;
mod contracts;
mod services;
mod repositories;
mod infrastructure;
#[tokio::main]
async fn main() {
// 1. 从过程宏自动收集所有 #[inject] 标注的服务
let provider = ServiceCollection::from_injected()
// 2. 补充手动注册的基础设施
.async_singleton(|_| Box::pin(async {
Arc::new(DbPool::connect("pg://localhost/app").await)
}))
.singleton(|_| Arc::new(AppConfig::from_env()))
.build_async()
.await
.unwrap();
// 3. 启动 Web 服务器,将 provider 注入 Axum State
let app = axum::Router::new()
.route("/users", axum::routing::post(create_user))
.with_state(provider);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
// Axum Handler:每个请求创建独立 Scope
async fn create_user(
axum::extract::State(provider): axum::extract::State<Arc<ServiceProvider>>,
axum::extract::Json(req): axum::extract::Json<CreateUserRequest>,
) -> axum::Json<User> {
let scope = provider.scope();
let user_svc: Arc<dyn IUserService> = scope.get().unwrap();
let user = user_svc.create(&req.name, &req.email).unwrap();
axum::Json(user)
}
关键设计决策
| 决策 | 原因 | |------|------| | `build_async()` | 数据库连接池需要异步初始化 | | 每个请求 `provider.scope()` | 请求级隔离,Scoped 服务(如 UnitOfWork)独立 | | Handler 从 Scope 解析 | 确保 Scoped 服务绑定到当前请求 Scope | | `#[inject(transient)]` 在 impl 上 | UserService 无状态,每次新建,便于 mock | | struct 上用 `#[derive(Inject)]` | 仅生成构造函数,不注册为具体类型 |
---
场景 2:电商订单处理全链路
业务需求
订单处理涉及多个服务协作:用户验证 → 库存检查 → 支付 → 物流,每个环节可独立替换。
合约层
use std::sync::Arc;
// 支付网关接口
pub trait IPaymentGateway: Send + Sync {
fn charge(&self, amount: u64) -> Result<String, String>;
fn name(&self) -> &'static str;
}
// 库存服务接口
pub trait IInventoryService: Send + Sync {
fn reserve(&self, product_id: u64, quantity: u32) -> Result<(), String>;
fn release(&self, product_id: u64, quantity: u32);
}
// 订单服务接口
pub trait IOrderService: Send + Sync {
fn place_order(&self, req: PlaceOrderRequest) -> Result<Order, String>;
}
pub struct PlaceOrderRequest {
pub user_id: u64,
pub product_id: u64,
pub quantity: u32,
pub payment_method: String,
}
pub struct Order {
pub id: u64,
pub status: OrderStatus,
pub amount: u64,
}
实现:策略模式 + 工厂模式
use rust_dix::*;
use std::sync::Arc;
// 支付网关:键控服务实现策略模式
#[derive(Inject)]
struct WechatPay;
#[inject]
impl IPaymentGateway for WechatPay {
fn charge(&self, amount: u64) -> Result<String, String> {
Ok(format!("微信支付 ¥{}", amount))
}
fn name(&self) -> &'static str { "wechat" }
}
#[derive(Inject)]
struct Alipay;
#[inject]
impl IPaymentGateway for Alipay {
fn charge(&self, amount: u64) -> Result<String, String> {
Ok(format!("支付宝 ¥{}", amount))
}
fn name(&self) -> &'static str { "alipay" }
}
// 订单服务:依赖注入 + 策略路由
#[derive(Inject)]
pub struct OrderService {
#[inject]
user_svc: Arc<dyn IUserService>,
#[inject]
inventory_svc: Arc<dyn IInventoryService>,
#[inject(provider)]
provider: Arc<ServiceProvider>, // 按需解析支付策略
}
#[inject(transient)]
impl IOrderService for OrderService {
fn place_order(&self, req: PlaceOrderRequest) -> Result<Order, String> {
// 1. 验证用户
let user = self.user_svc.find(req.user_id)?;
// 2. 预留库存
self.inventory_svc.reserve(req.product_id, req.quantity)?;
// 3. 按支付方式选择网关(策略路由)
let gateway: Arc<dyn IPaymentGateway> = self.provider
.try_get_keyed(&req.payment_method)
.unwrap_or_else(|| self.provider.get().unwrap());
let amount = req.quantity as u64 * 100;
let tx_id = gateway.charge(amount)?;
// 4. 创建订单
Ok(Order {
id: 1,
status: OrderStatus::Paid,
amount,
})
}
}
组合根
let provider = ServiceCollection::from_injected()
// 键控服务:多支付网关
.keyed_singleton::<dyn IPaymentGateway>("wechat", |_| Arc::new(WechatPay))
.keyed_singleton::<dyn IPaymentGateway>("alipay", |_| Arc::new(Alipay))
.build()
.unwrap();
测试:Mock 替换策略
#[cfg(test)]
mod tests {
use super::*;
struct MockPaymentGateway;
impl IPaymentGateway for MockPaymentGateway {
fn charge(&self, _amount: u64) -> Result<String, String> {
Ok("mock_tx_001".into())
}
fn name(&self) -> &'static str { "mock" }
}
#[test]
fn test_place_order() {
let provider = ServiceCollection::new()
.singleton::<dyn IUserService>(|_| Arc::new(MockUserService))
.singleton::<dyn IInventoryService>(|_| Arc::new(MockInventoryService))
.keyed_singleton::<dyn IPaymentGateway>("wechat", |_| Arc::new(MockPaymentGateway))
.transient::<dyn IOrderService>(|p| Arc::new(OrderService::new(
p.get().unwrap(),
p.get().unwrap(),
Arc::clone(p.provider_arc().as_ref().unwrap()),
)))
.build()
.unwrap();
let order_svc: Arc<dyn IOrderService> = provider.get().unwrap();
let result = order_svc.place_order(PlaceOrderRequest {
user_id: 1,
product_id: 100,
quantity: 2,
payment_method: "wechat".into(),
});
assert!(result.is_ok());
}
}
---
场景 3:多租户 SaaS 架构
架构特点
- 每个租户独立数据库(或 schema) - 请求时根据租户 ID 动态选择数据源 - 租户间服务共享,数据隔离
实现
use rust_dix::*;
use std::sync::Arc;
use std::collections::HashMap;
// 多租户连接池:按租户存储连接
struct TenantDbPool {
pools: HashMap<String, Arc<DbPool>>,
default: Arc<DbPool>,
}
impl TenantDbPool {
fn resolve(&self, tenant_id: &str) -> Arc<DbPool> {
self.pools.get(tenant_id)
.cloned()
.unwrap_or_else(|| Arc::clone(&self.default))
}
}
// 仓储:根据租户上下文动态选择数据源
#[derive(Inject)]
struct TenantAwareRepo {
#[inject(provider)]
provider: Arc<ServiceProvider>,
}
impl TenantAwareRepo {
fn get_db(&self) -> Arc<DbPool> {
let tenant_pool: Arc<TenantDbPool> = self.provider.get().unwrap();
let tenant_id = self.get_current_tenant();
tenant_pool.resolve(&tenant_id)
}
}
// 请求级 Scope:每个请求绑定租户上下文
#[inject(scoped)]
struct TenantContext {
tenant_id: String,
tenant_db: Arc<DbPool>,
}
// 组合根
let provider = ServiceCollection::new()
.async_singleton(|_| Box::pin(async {
let mut pools = HashMap::new();
pools.insert("tenant_a".into(), Arc::new(DbPool::connect("pg://tenant-a").await));
pools.insert("tenant_b".into(), Arc::new(DbPool::connect("pg://tenant-b").await));
Arc::new(TenantDbPool {
pools,
default: Arc::new(DbPool::connect("pg://default").await),
})
}))
.scoped(|r| {
let tenant_pool: Arc<TenantDbPool> = r.get().unwrap();
let tenant_id = "tenant_a".into(); // 实际从请求头解析
Arc::new(TenantContext {
tenant_db: tenant_pool.resolve(&tenant_id),
tenant_id,
})
})
.build_async()
.await
.unwrap();
// 请求处理
fn handle_request(provider: &Arc<ServiceProvider>, tenant_id: &str) {
let scope = provider.scope();
let ctx: Arc<TenantContext> = scope.get().unwrap();
// ctx.tenant_db 指向该租户的数据库
let repo: Arc<TenantAwareRepo> = scope.get().unwrap();
// repo 可通过 provider 获取 ctx 拿到租户数据库
}
关键设计决策
| 决策 | 原因 | |------|------| | `TenantContext` 注册为 Scoped | 每个请求绑定一个租户,Scope 内共享 | | `TenantDbPool` 注册为 Singleton | 全局共享连接池映射 | | `#[inject(provider)]` 注入 Provider | 仓储需要动态解析当前租户上下文 |
---
场景 4:插件系统架构
需求
- 宿主程序提供核心功能(事件总线、配置) - 插件通过 `cdylib` 动态加载 - 每个插件有独立 DI 容器,但可访问宿主服务
宿主端
use rust_dix::*;
use std::sync::Arc;
// 定义插件接口(在宿主 crate 中)
pub trait IPlugin: Send + Sync {
fn name(&self) -> &'static str;
fn init(&self, host: Arc<dyn IHostServices>);
fn execute(&self, input: &str) -> String;
}
pub trait IHostServices: Send + Sync {
fn get_event_bus(&self) -> Arc<EventBus>;
fn get_config(&self) -> Arc<AppConfig>;
}
// 宿主容器
let host_provider = ServiceCollection::new()
.singleton(|_| Arc::new(EventBus::new()))
.singleton(|_| Arc::new(AppConfig::from_env()))
.build()
.unwrap();
// 注册命名服务供插件访问(跨 DLL 必须用命名服务)
host_provider.register_named("event_bus", host_provider.get::<EventBus>().unwrap());
host_provider.register_named("config", host_provider.get::<AppConfig>().unwrap());
// 插件容器(每个插件独立)
let plugin_provider = ServiceCollection::new()
.singleton::<dyn IPlugin>(|_| Arc::new(MyPlugin::new()))
.build()
.unwrap();
// 分层容器:插件优先,宿主回退
let wrapper = ServiceProviderWrapper::new(plugin_provider, host_provider);
// 插件解析:自己的服务优先,找不到回退宿主
let plugin: Arc<dyn IPlugin> = wrapper.get().unwrap(); // 从插件容器解析
let bus: Arc<EventBus> = wrapper.get_named("event_bus").unwrap(); // 回退宿主的命名服务
插件端(cdylib)
// 插件 crate 中
use rust_dix::*;
use std::sync::Arc;
#[derive(Inject)]
struct MyPlugin {
// 插件自己的服务
#[inject]
handler: Arc<PluginHandler>,
}
#[inject]
impl IPlugin for MyPlugin {
fn name(&self) -> &'static str { "my_plugin" }
fn init(&self, host: Arc<dyn IHostServices>) {
let bus = host.get_event_bus();
bus.subscribe("order.created", |data| self.handler.process(data));
}
fn execute(&self, input: &str) -> String {
self.handler.process(input)
}
}
关键设计决策
| 决策 | 原因 | |------|------| | 命名服务而非 TypeId | 跨 DLL 编译单元 TypeId 不同,无法用类型解析 | | `ServiceProviderWrapper` 分层 | 插件优先,宿主回退,天然隔离 | | 插件接口定义在宿主 | 宿主控制插件契约,插件只实现 |
---
场景 5:测试驱动开发(TDD)
测试策略层次
#[cfg(test)]
mod tests {
use rust_dix::*;
use std::sync::Arc;
// 1. Mock 实现
struct MockUserRepo {
users: std::sync::Mutex<Vec<User>>,
}
impl IUserRepo for MockUserRepo {
fn find_by_id(&self, id: u64) -> Result<User, String> {
self.users.lock().unwrap()
.iter()
.find(|u| u.id == id)
.cloned()
.ok_or("not found".into())
}
fn insert(&self, name: &str, email: &str) -> Result<User, String> {
let user = User { id: 1, name: name.into(), email: email.into() };
self.users.lock().unwrap().push(user.clone());
Ok(user)
}
}
// 2. 测试容器工厂
fn test_provider() -> ServiceProvider {
ServiceCollection::new()
.singleton::<dyn IUserRepo>(|_| Arc::new(MockUserRepo {
users: std::sync::Mutex::new(vec![]),
}))
.transient::<dyn IUserService>(|p| Arc::new(UserService::new(
p.get().unwrap(),
)))
.build()
.unwrap()
}
#[test]
fn test_create_user() {
let provider = test_provider();
let user_svc: Arc<dyn IUserService> = provider.get().unwrap();
let user = user_svc.create("Alice", "alice@example.com").unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "alice@example.com");
}
#[test]
fn test_find_user_not_found() {
let provider = test_provider();
let user_svc: Arc<dyn IUserService> = provider.get().unwrap();
let result = user_svc.find(999);
assert!(result.is_err());
}
}
测试策略决策表
| 测试层次 | 如何构建容器 | Mock 策略 | |---------|------------|----------| | 单元测试(Service) | 手动构建,只注册被测服务 + Mock | 所有依赖替换为 Mock | | 集成测试(Handler) | 手动构建,注册真实 Repo + Mock 外部服务 | 外部服务 Mock,内部服务真实 | | 端到端测试 | `from_injected()` + 覆盖基础设施 | 仅替换外部 API 适配器 |
---
生命周期选择速查
| 服务类型 | 生命周期 | 示例 | |---------|---------|------| | 数据库连接池 | Singleton (async) | `DbPool`、`RedisClient` | | 全局配置 | Singleton | `AppConfig`、`FeatureFlags` | | 事件总线 | Singleton | `EventBus`、`MessageQueue` | | 请求上下文 | Scoped | `TenantContext`、`RequestContext` | | 工作单元 / 事务 | Scoped | `UnitOfWork`、`DbContext` | | 业务服务(无状态) | Transient | `UserService`、`OrderService` | | 校验器 | Transient | `EmailValidator`、`PasswordValidator` | | 策略实现 | 按需(Singleton/Transient) | 支付网关、通知渠道 |
---
反模式速查
贯穿上述所有场景的通用反模式:
| 反模式 | 错误做法 | 正确做法 | |--------|---------|---------| | 依赖具体类型 | `Arc<UserService>` | `Arc<dyn IUserService>` | | 注册为具体类型 | `.singleton(\|_\| Arc::new(Impl))` | `.singleton::<dyn Trait>(...)` | | 传递 Provider 到处使用 | `provider: Arc<ServiceProvider>` 到处传 | 显式依赖字段 | | 每次请求重建容器 | 请求内 `build()` | 构建一次,`scope()` 创建 Scope | | Singleton 依赖 Scoped | `#[inject(singleton)]` 包含 Scoped 字段 | Singleton 只依赖 Singleton | | struct 与 impl 同时 `#[inject]` | 双重注册 | struct 用 `#[derive(Inject)]`,impl 用 `#[inject]` |
---
下一节:[性能优化建议](performance-tips.md)