跳至主要內容

EDD 依赖注入策略指南

Mr.Lexon大约 7 分钟EDD

Title

EDD 依赖注入策略指南:基于实践的简化方法

一、核心认知

理论与现实的差距

在 EDD 实践中,我们发现了一个关键矛盾:

  • App 层希望:细粒度的依赖注入,清晰的职责分离
  • Infra 层现实:同一实体的多个 trait 最终由同一个 Service 实现
// App 层的理想
pub async fn register_user(
    input: RegisterInput,
    repo: &impl UserRepository,
    validator: &impl UserValidator,
    cache: &impl UserCache,
) -> Result<Outcome<User>, AppError>

// 实际调用时
register_user(
    input,
    &*app.user_service,  // 都是
    &*app.user_service,  // 同一个
    &*app.user_service,  // Service!
).await

// 本质上等同于
register_user<S: UserRepository + UserValidator + UserCache>(
    input,
    &*app.user_service,
).await

核心原则

  1. 凡是注入的,都是必要的 - 不使用 Option,所有依赖都是必需的
  2. 承认耦合的现实 - 同一实体的不同 trait 本质是不同视角,不是真正的分离
  3. 区分真假依赖 - 实体内聚的是假分离,跨领域的是真分离
  4. 编译期优化优先 - 使用 &impl Trait 而非 &dyn Trait
  5. 简单直接 - 不过度设计,接受 Rust 的简洁哲学

二、依赖分类

真依赖 vs 假依赖

// 假依赖:同一实体的不同视角
pub trait UserRepository { ... }     // User 实体的持久化视角
pub trait UserValidator { ... }      // User 实体的验证视角  
pub trait UserCache { ... }          // User 实体的缓存视角
// 这些最终都由 UserServiceImpl 实现

// 真依赖:独立的外部服务
pub trait EmailService { ... }       // 独立的邮件服务
pub trait PaymentGateway { ... }     // 独立的支付网关
pub trait SMSProvider { ... }        // 独立的短信服务
// 这些由不同的服务实现

三、简化后的注入策略

基于实践,我们简化为三种模式:

模式 1:简单参数(1-2个依赖)

适用场景

  • 简单查询或操作
  • 依赖明确且独立
  • 不需要复杂协作

代码示例

// ✅ 简单查询
pub async fn get_user(
    id: UserId,
    repo: &impl UserRepository,
) -> Result<User, AppError> {
    repo.find(id).await
}

// ✅ 简单验证
pub async fn check_username_available(
    username: &str,
    repo: &impl UserRepository,
) -> Result<bool, AppError> {
    Ok(!repo.exists(username).await?)
}

模式 2:HRTB(同一实体的多个操作)

适用场景

  • 同一实体/聚合的复杂操作
  • 需要实体的多个视角(Repository + Validator + Cache)
  • 这些 trait 在 Infra 层由同一个 Service 实现

代码示例

// ✅ User 实体的复杂操作
pub async fn register_user<S>(
    input: RegisterInput,
    user_service: &S,
) -> Result<Outcome<User>, AppError>
where
    S: UserRepository + UserValidator + UserCache,
{
    // 承认这些 trait 本质是一体的
    user_service.validate(&input)?;
    
    if user_service.exists(&input.username).await? {
        return Err(AppError::UsernameTaken);
    }
    
    let user = User::new(input);
    user_service.save(&user).await?;
    user_service.cache_user(&user).await?;
    
    Ok(Outcome {
        data: user,
        from_case: AppUseCase::RegisterUser,
        events: vec![AppEvent::UserRegistered],
    })
}

// ✅ Order 实体的复杂操作
pub async fn process_order<S>(
    order_id: OrderId,
    order_service: &S,
) -> Result<Outcome<Order>, AppError>
where
    S: OrderRepository + OrderValidator + InventoryChecker,
{
    let order = order_service.get_order(order_id).await?;
    order_service.validate_order(&order)?;
    order_service.check_inventory(&order.items).await?;
    order_service.update_status(order_id, Status::Processed).await?;
    
    Ok(Outcome { 
        data: order,
        from_case: AppUseCase::ProcessOrder,
        events: vec![AppEvent::OrderProcessed(order_id)],
    })
}

模式 3:Context(跨领域编排)

适用场景

  • 涉及多个独立领域/服务
  • 需要协调不同的业务能力
  • 真正独立的外部服务

代码示例

// 定义领域上下文(每个上下文是一个实体的所有能力)
pub struct UserContext<'a, S: UserRepository + UserValidator + UserCache> {
    service: &'a S,
}

pub struct OrderContext<'a, S: OrderRepository + OrderValidator> {
    service: &'a S,
}

// ✅ 跨领域的复杂流程
pub async fn complete_purchase<U, O>(
    user_id: UserId,
    order_id: OrderId,
    user_ctx: UserContext<'_, U>,
    order_ctx: OrderContext<'_, O>,
    payment: &impl PaymentGateway,  // 真正独立的服务
    email: &impl EmailService,      // 真正独立的服务
) -> Result<Outcome<Receipt>, AppError>
where
    U: UserRepository + UserValidator + UserCache,
    O: OrderRepository + OrderValidator,
{
    // User 领域操作
    let user = user_ctx.service.get_user(user_id).await?;
    user_ctx.service.validate_payment_eligible(&user)?;
    
    // Order 领域操作
    let order = order_ctx.service.get_order(order_id).await?;
    order_ctx.service.validate_order(&order)?;
    
    // 独立服务调用
    let payment_result = payment.process(&order.payment_info).await?;
    
    // 更新状态
    order_ctx.service.mark_as_paid(order_id, &payment_result).await?;
    
    // 通知服务
    let events = vec![
        AppEvent::SendReceipt { 
            email: user.email.clone(),
            order_id,
        },
    ];
    
    Ok(Outcome {
        data: Receipt::new(order, payment_result),
        from_case: AppUseCase::CompletePurchase,
        events,
    })
}

四、Infra 层实现指南

实体 Service 实现(聚合模式)

// 同一实体的所有 trait 由一个 Service 实现
#[derive(Clone)]
pub struct UserServiceImpl {
    db: Arc<PgPool>,
    cache: Arc<Redis>,
    config: Arc<UserConfig>,
}

// 实现该实体的所有视角
impl UserRepository for UserServiceImpl {
    async fn find(&self, id: UserId) -> Result<User> {
        // 使用 self.db
    }
    
    async fn exists(&self, username: &str) -> Result<bool> {
        // 使用 self.db
    }
}

impl UserValidator for UserServiceImpl {
    async fn validate(&self, input: &RegisterInput) -> Result<()> {
        // 可能需要查询 self.db
        if self.exists(&input.username).await? {
            return Err(ValidationError::UsernameTaken);
        }
        Ok(())
    }
}

impl UserCache for UserServiceImpl {
    async fn cache_user(&self, user: &User) -> Result<()> {
        // 使用 self.cache
    }
}

独立服务实现

// 真正独立的服务单独实现
#[derive(Clone)]
pub struct EmailServiceImpl {
    client: Arc<SendGridClient>,
    templates: Arc<EmailTemplates>,
}

impl EmailService for EmailServiceImpl {
    async fn send(&self, to: &str, template: Template) -> Result<()> {
        self.client.send(to, template).await
    }
}

// 支付服务
#[derive(Clone)]
pub struct PaymentGatewayImpl {
    stripe: Arc<StripeClient>,
}

impl PaymentGateway for PaymentGatewayImpl {
    async fn process(&self, payment: &PaymentInfo) -> Result<PaymentResult> {
        self.stripe.charge(payment).await
    }
}

五、Web 层组织

AppState 结构

#[derive(Clone)]
pub struct AppState {
    // 实体 Services(聚合了多个 trait)
    user_service: Arc<UserServiceImpl>,
    order_service: Arc<OrderServiceImpl>,
    
    // 独立 Services
    email_service: Arc<EmailServiceImpl>,
    payment_gateway: Arc<PaymentGatewayImpl>,
    sms_provider: Arc<SMSProviderImpl>,
    
    // 基础设施
    event_bus: Arc<EventBus>,
}

Handler 调用示例

// 简单操作
async fn get_user_handler(
    State(app): State<Arc<AppState>>,
    Path(id): Path<UserId>,
) -> Result<Json<User>, ApiError> {
    let user = get_user(id, &*app.user_service).await?;
    Ok(Json(user))
}

// HRTB 操作
async fn register_handler(
    State(app): State<Arc<AppState>>,
    Json(input): Json<RegisterInput>,
) -> Result<Json<User>, ApiError> {
    // 一个参数,多个能力
    let outcome = register_user(input, &*app.user_service).await?;
    
    app.event_bus.dispatch(outcome.events).await;
    Ok(Json(outcome.data))
}

// Context 操作
async fn purchase_handler(
    State(app): State<Arc<AppState>>,
    Json(req): Json<PurchaseRequest>,
) -> Result<Json<Receipt>, ApiError> {
    let user_ctx = UserContext {
        service: &*app.user_service,
    };
    
    let order_ctx = OrderContext {
        service: &*app.order_service,
    };
    
    let outcome = complete_purchase(
        req.user_id,
        req.order_id,
        user_ctx,
        order_ctx,
        &*app.payment_gateway,  // 独立服务
        &*app.email_service,     // 独立服务
    ).await?;
    
    app.event_bus.dispatch(outcome.events).await;
    Ok(Json(outcome.data))
}

六、选择决策树

开始评估 UseCase
    ↓
涉及几个实体/领域?
│
├─ 单一实体
│   ↓
│   需要该实体的几个能力?
│   ├─ 1-2个 → 【简单参数】
│   └─ 3个以上 → 【HRTB】
│
└─ 多个实体/领域
    ↓
    是否包含独立外部服务?
    ├─ 仅实体间协作 → 【Context(仅实体)】
    └─ 包含外部服务 → 【Context + 独立参数】

七、测试策略

简单参数测试

#[test]
async fn test_get_user() {
    let mut mock = MockUserRepository::new();
    mock.expect_find().return_const(Ok(test_user()));
    
    let result = get_user(user_id, &mock).await;
    assert!(result.is_ok());
}

HRTB 测试

// Mock 整个 Service
struct MockUserService;

impl UserRepository for MockUserService {
    async fn find(&self, _: UserId) -> Result<User> {
        Ok(test_user())
    }
}

impl UserValidator for MockUserService {
    async fn validate(&self, _: &RegisterInput) -> Result<()> {
        Ok(())
    }
}

impl UserCache for MockUserService {
    async fn cache_user(&self, _: &User) -> Result<()> {
        Ok(())
    }
}

#[test]
async fn test_register() {
    let mock = MockUserService;
    let result = register_user(input, &mock).await;
    assert!(result.is_ok());
}

Context 测试

#[test]
async fn test_purchase() {
    let user_ctx = UserContext {
        service: &MockUserService,
    };
    
    let order_ctx = OrderContext {
        service: &MockOrderService,
    };
    
    let mock_payment = MockPaymentGateway::new();
    let mock_email = MockEmailService::new();
    
    let result = complete_purchase(
        user_id,
        order_id,
        user_ctx,
        order_ctx,
        &mock_payment,
        &mock_email,
    ).await;
    
    assert!(result.is_ok());
}

八、关键洞察

1. 接受耦合的现实

// 不要强行分离本质上耦合的东西
// UserRepository 和 UserValidator 本来就是一体的两面

2. 区分真假分离

// 假分离:同一实体的不同视角 → 用 HRTB
// 真分离:独立的外部服务 → 用独立参数

3. 简单优于复杂

// Rust 不是 Java
// 不需要过度的抽象和分离
// 直接、简单、高效

九、常见问题

Q1: 为什么不坚持细粒度分离?

A: 因为 Infra 层的现实是这些 trait 共享资源,强行分离会导致资源重复和协作困难。

Q2: 这样不是违背了单一职责原则吗?

A: 单一职责是指"一个类应该只有一个改变的理由"。UserServiceImpl 的改变理由是"User 实体的业务规则变化",这仍然是单一的。

Q3: 测试会不会变困难?

A: HRTB 确实需要 mock 多个 trait,但这些 trait 本来就是相关的。如果测试困难,可能说明这些 trait 确实应该分离(即它们是真依赖)。

Q4: 什么时候应该真正分离实现?

A: 当你需要运行时替换策略时。比如:开发环境用 MockEmailService,生产环境用 SendGridService。

Q5: Context 模式会不会太复杂?

A: Context 只在跨领域编排时使用。如果觉得复杂,可以先用简单参数,随着复杂度增长再重构。

十、总结

EDD 依赖注入的核心认知:

  1. 承认现实 - 同一实体的 trait 自然耦合
  2. 区分真假 - 真分离用独立参数,假分离用 HRTB
  3. 保持简单 - 不过度设计,够用就好
  4. 逐步演进 - 从简单参数开始,需要时再升级

记住:架构服务于问题,而非问题服从于架构

上次编辑于:
贡献者: lexon