Skip to content

最佳实践 ​

🎯 MediatR 开发的行业最佳实践和规范


📖 概述 ​

遵循最佳实践可以确保 MediatR 应用的可维护性、性能和可扩展性。本章总结了来自实际项目的经验和教训。


📝 命名约定 ​

请求命名 ​

csharp
// ✅ 推荐:使用 Command/Query 后缀
public class CreateOrderCommand : IRequest<OrderResult> { }
public class GetOrderQuery : IRequest<OrderDto> { }
public class UpdateUserCommand : IRequest { }
public class DeleteProductCommand : IRequest { }

// ❌ 避免:模糊的命名
public class OrderRequest : IRequest<OrderResult> { } // 不清楚是命令还是查询
public class UserData : IRequest<UserDto> { } // 不明确

Handler 命名 ​

csharp
// ✅ 推荐:与请求对应
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult> { }
public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto> { }

// ❌ 避免:通用命名
public class OrderHandler : IRequestHandler<CreateOrderCommand, OrderResult> { } // 不清晰
public class Handler : IRequestHandler<GetOrderQuery, OrderDto> { } // 太泛

通知命名 ​

csharp
// ✅ 推荐:使用 Event/Notification 后缀
public class OrderCreatedEvent : INotification { }
public class UserRegisteredNotification : INotification { }
public class PaymentProcessedEvent : INotification { }

// ❌ 避免
public class OrderMessage : INotification { } // 不清楚

管道行为命名 ​

csharp
// ✅ 推荐:使用 Behavior 后缀
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> { }
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> { }
public class CachingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> { }

🎯 Handler 单一职责原则 ​

✅ 正确做法 ​

csharp
// 每个 Handler 只处理一种请求
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 只负责创建订单
        var order = new Order { /* ... */ };
        await _repo.AddAsync(order, ct);
        return new OrderResult { OrderId = order.Id };
    }
}

public class SendOrderConfirmationHandler : INotificationHandler<OrderCreatedEvent>
{
    public async Task Handle(OrderCreatedEvent notification, CancellationToken ct)
    {
        // 只负责发送邮件
        await _emailService.SendAsync(notification.CustomerEmail);
    }
}

❌ 错误做法 ​

csharp
// 一个 Handler 处理多种职责
public class OrderHandler : 
    IRequestHandler<CreateOrderCommand>,
    IRequestHandler<UpdateOrderCommand>,
    INotificationHandler<OrderCreatedEvent>
{
    // 违反单一职责原则,难以维护和测试
}

🔧 避免在 Handler 中直接依赖多个服务 ​

❌ 反模式 ​

csharp
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepo;
    private readonly ICustomerRepository _customerRepo;
    private readonly IProductRepository _productRepo;
    private readonly IPaymentService _paymentService;
    private readonly IEmailService _emailService;
    private readonly IInventoryService _inventoryService;
    private readonly IAuditService _auditService;
    private readonly ILogger<CreateOrderHandler> _logger;
    
    // 构造函数参数过多(8个)
    public CreateOrderHandler(
        IOrderRepository orderRepo,
        ICustomerRepository customerRepo,
        IProductRepository productRepo,
        IPaymentService paymentService,
        IEmailService emailService,
        IInventoryService inventoryService,
        IAuditService auditService,
        ILogger<CreateOrderHandler> logger)
    {
        // ...
    }
}

问题:

  • 违反单一职责原则
  • 难以测试
  • 耦合度高

✅ 推荐做法:引入服务层 ​

csharp
// 提取领域服务
public class OrderProcessingService
{
    private readonly IOrderRepository _orderRepo;
    private readonly IPaymentService _paymentService;
    private readonly IInventoryService _inventoryService;

    public OrderProcessingService(
        IOrderRepository orderRepo,
        IPaymentService paymentService,
        IInventoryService inventoryService)
    {
        _orderRepo = orderRepo;
        _paymentService = paymentService;
        _inventoryService = inventoryService;
    }

    public async Task<Order> ProcessOrder(CreateOrderCommand command, CancellationToken ct)
    {
        var order = await CreateOrder(command, ct);
        await _paymentService.ProcessAsync(order.Id, ct);
        await _inventoryService.ReserveAsync(order.Items, ct);
        return order;
    }
}

// Handler 变得简洁
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly OrderProcessingService _orderProcessingService;
    private readonly IMediator _mediator;

    public CreateOrderHandler(
        OrderProcessingService orderProcessingService,
        IMediator mediator)
    {
        _orderProcessingService = orderProcessingService;
        _mediator = mediator;
    }

    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        var order = await _orderProcessingService.ProcessOrder(request, ct);
        
        // 发布事件
        await _mediator.Publish(new OrderCreatedEvent 
        { 
            OrderId = order.Id 
        }, ct);
        
        return new OrderResult { OrderId = order.Id };
    }
}

优势:

  • ✅ Handler 职责清晰
  • ✅ 领域服务可复用
  • ✅ 易于测试
  • ✅ 符合 DDD 原则

🔄 管道行为的设计原则 ​

原则 1:无状态 ​

csharp
// ✅ 正确:无状态行为
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger; // 只注入依赖,不保存状态
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        // 不使用实例字段保存请求/响应数据
        _logger.LogInformation("Processing: {Request}", request);
        return await next(ct);
    }
}

// ❌ 错误:有状态行为
public class BadBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private List<string> _requestHistory = new(); // ❌ 保存状态,线程不安全

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        _requestHistory.Add(request.ToString()); // ❌ 竞态条件
        return await next(ct);
    }
}

原则 2:幂等性 ​

csharp
// ✅ 正确:幂等行为(多次执行结果相同)
public class IdempotentCachingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        var cacheKey = GenerateCacheKey(request);
        
        if (_cache.TryGetValue(cacheKey, out TResponse cached))
            return cached;

        var response = await next(ct);
        _cache.Set(cacheKey, response);
        
        return response; // 无论执行多少次,结果一致
    }
}

原则 3:快速失败 ​

csharp
// ✅ 正确:验证失败立即返回
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        var validationResult = await _validator.ValidateAsync(request, ct);
        
        if (!validationResult.IsValid)
        {
            // 快速失败,不执行后续逻辑
            throw new ValidationException(validationResult.Errors);
        }

        return await next(ct);
    }
}

⚠️ 异常处理策略 ​

哪些在 Behavior 中处理 ​

csharp
// ✅ 在 Behavior 中处理横切关注点的异常
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        try
        {
            var validationResult = await _validator.ValidateAsync(request, ct);
            
            if (!validationResult.IsValid)
                throw new ValidationException(validationResult.Errors); // 验证异常

            return await next(ct);
        }
        catch (ValidationException)
        {
            throw; // 重新抛出,让全局处理器处理
        }
    }
}

哪些在 Handler 中处理 ​

csharp
// ✅ 在 Handler 中处理业务逻辑异常
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 业务规则验证
        if (request.Quantity > MaxQuantity)
            throw new BusinessException("超过最大购买数量"); // 业务异常

        // 领域逻辑
        var order = new Order { /* ... */ };
        
        // 持久化异常由事务行为处理
        await _repo.AddAsync(order, ct);
        
        return new OrderResult { OrderId = order.Id };
    }
}

全局异常处理 ​

csharp
// 中间件中的全局异常处理
app.UseExceptionHandler(appError =>
{
    appError.Run(async context =>
    {
        var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
        var exception = contextFeature?.Error;

        context.Response.StatusCode = exception switch
        {
            ValidationException => 400,
            NotFoundException => 404,
            BusinessException => 422,
            _ => 500
        };

        await context.Response.WriteAsJsonAsync(new
        {
            error = exception?.Message,
            type = exception?.GetType().Name
        });
    });
});

🚫 避免过度使用 MediatR ​

何时使用 MediatR ​

✅ 适合的场景:

  • 复杂的业务逻辑
  • 需要解耦的模块
  • CQRS 架构
  • 领域事件驱动
  • 团队协作的大型项目
  • 需要横切关注点(日志、验证、事务)

何时不使用 MediatR ​

❌ 不适合的场景:

1. 简单 CRUD ​

csharp
// ❌ 过度设计:简单的查询
public class GetUserQuery : IRequest<UserDto>
{
    public Guid UserId { get; set; }
}

public class GetUserHandler : IRequestHandler<GetUserQuery, UserDto>
{
    public async Task<UserDto> Handle(GetUserQuery request, CancellationToken ct)
    {
        return await _dbContext.Users
            .Where(u => u.Id == request.UserId)
            .Select(u => new UserDto { /* ... */ })
            .FirstOrDefaultAsync(ct);
    }
}

// ✅ 更简单:直接调用
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(Guid id)
{
    var user = await _dbContext.Users
        .Where(u => u.Id == id)
        .Select(u => new UserDto { /* ... */ })
        .FirstOrDefaultAsync();
    
    return user is null ? NotFound() : Ok(user);
}

2. 性能极度敏感 ​

csharp
// ❌ 高频交易场景(纳秒级要求)
for (int i = 0; i < 1_000_000; i++)
{
    await _mediator.Send(new TradeCommand()); // 100ns 开销累积
}

// ✅ 直接调用
for (int i = 0; i < 1_000_000; i++)
{
    await _tradingService.ExecuteTrade(); // 零开销
}

3. 小型个人项目 ​

// 如果项目只有几个页面,引入 MediatR 可能增加不必要的复杂度

📋 文档与团队规范 ​

代码注释规范 ​

csharp
/// <summary>
/// 创建订单命令
/// </summary>
public class CreateOrderCommand : IRequest<OrderResult>
{
    /// <summary>
    /// 客户ID
    /// </summary>
    public Guid CustomerId { get; set; }

    /// <summary>
    /// 订单项列表
    /// </summary>
    public List<OrderItemCommand> Items { get; set; }
}

/// <summary>
/// 创建订单处理器
/// </summary>
/// <remarks>
/// 负责协调订单创建、支付处理和库存预留
/// </remarks>
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    /// <summary>
    /// 处理创建订单请求
    /// </summary>
    /// <param name="request">创建订单命令</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>订单结果</returns>
    /// <exception cref="ValidationException">当参数验证失败时</exception>
    /// <exception cref="BusinessException">当业务规则违反时</exception>
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        // 实现...
    }
}

团队规范文档 ​

markdown
# MediatR 开发规范

## 1. 命名规范
- 命令以 `Command` 结尾
- 查询以 `Query` 结尾
- Handler 以 `Handler` 结尾
- 事件以 `Event` 或 `Notification` 结尾

## 2. 文件组织
- 每个 Command/Query 单独文件夹
- Handler 与请求放在同一文件夹
- Validator 与请求放在同一文件夹

## 3. 代码审查清单
- [ ] Handler 是否单一职责
- [ ] 是否使用了管道行为处理横切关注点
- [ ] 是否有适当的异常处理
- [ ] 是否编写了单元测试
- [ ] 是否符合命名规范

🎯 最佳实践总结 ​

Do's ✅ ​

  1. ✅ 使用清晰的命名约定
  2. ✅ 保持 Handler 单一职责
  3. ✅ 使用管道行为处理横切关注点
  4. ✅ 引入领域服务简化 Handler
  5. ✅ 编写单元测试
  6. ✅ 使用异步 API
  7. ✅ 合理缓存
  8. ✅ 记录详细的日志
  9. ✅ 遵循团队规范
  10. ✅ 文档化复杂逻辑

Don'ts ❌ ​

  1. ❌ 不要在 Handler 中注入过多依赖
  2. ❌ 不要在有状态的行为中保存数据
  3. ❌ 不要吞掉异常
  4. ❌ 不要在简单 CRUD 中使用 MediatR
  5. ❌ 不要使用同步阻塞操作
  6. ❌ 不要让 Handler 处理多种请求
  7. ❌ 不要在 Behavior 中执行业务逻辑
  8. ❌ 不要忽略性能监控
  9. ❌ 不要跳过单元测试
  10. ❌ 不要过度设计

🎓 总结 ​

遵循这些最佳实践可以确保:

✅ 代码质量高 - 清晰、可维护
✅ 性能优秀 - 高效、可扩展
✅ 易于测试 - 隔离、可靠
✅ 团队协作顺畅 - 规范、一致


💡 提示: 最佳实践不是教条,要根据项目实际情况灵活应用!

Released under the CC BY-SA 4.0 License.