EDD 依赖注入策略指南
大约 7 分钟
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
核心原则
- 凡是注入的,都是必要的 - 不使用 Option,所有依赖都是必需的
- 承认耦合的现实 - 同一实体的不同 trait 本质是不同视角,不是真正的分离
- 区分真假依赖 - 实体内聚的是假分离,跨领域的是真分离
- 编译期优化优先 - 使用
&impl Trait而非&dyn Trait - 简单直接 - 不过度设计,接受 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 依赖注入的核心认知:
- 承认现实 - 同一实体的 trait 自然耦合
- 区分真假 - 真分离用独立参数,假分离用 HRTB
- 保持简单 - 不过度设计,够用就好
- 逐步演进 - 从简单参数开始,需要时再升级
记住:架构服务于问题,而非问题服从于架构。