Skip to content

简单三层架构引入 MediatR ​

🔄 将传统三层架构重构为 MediatR 模式


📖 场景描述 ​

假设你有一个传统的三层架构应用:

TraditionalApp/
├── Controllers/
│   └── OrdersController.cs
├── Services/
│   └── OrderService.cs
├── Repositories/
│   └── OrderRepository.cs
└── Models/
    └── Order.cs

问题:

  • ❌ 控制器直接依赖多个服务
  • ❌ 服务之间相互调用,耦合严重
  • ❌ 横切关注点(日志、验证)分散在各处
  • ❌ 难以测试和维护

目标:使用 MediatR 重构,实现解耦和标准化。


🔧 改造步骤 ​

步骤 1:安装 MediatR ​

bash
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
dotnet add package FluentValidation.AspNetCore

步骤 2:重构前 - 传统三层架构 ​

控制器(改造前) ​

csharp
// Controllers/OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly OrderService _orderService;
    private readonly PaymentService _paymentService;
    private readonly EmailService _emailService;
    private readonly InventoryService _inventoryService;
    private readonly ILogger<OrdersController> _logger;

    public OrdersController(
        OrderService orderService,
        PaymentService paymentService,
        EmailService emailService,
        InventoryService inventoryService,
        ILogger<OrdersController> logger)
    {
        _orderService = orderService;
        _paymentService = paymentService;
        _emailService = emailService;
        _inventoryService = inventoryService;
        _logger = logger;
    }

    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
    {
        _logger.LogInformation("开始创建订单: {ProductId}", request.ProductId);

        try
        {
            // 验证
            if (request.Quantity <= 0)
                return BadRequest("数量必须大于0");

            // 创建订单
            var order = await _orderService.CreateOrder(request);

            // 处理支付
            await _paymentService.ProcessPayment(order.Id);

            // 更新库存
            await _inventoryService.UpdateStock(order.Items);

            // 发送邮件
            await _emailService.SendConfirmation(order.CustomerEmail);

            _logger.LogInformation("订单创建成功: {OrderId}", order.Id);

            return Ok(new { OrderId = order.Id });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "订单创建失败");
            return StatusCode(500, "服务器错误");
        }
    }
}

问题:

  • 控制器职责过多(验证、协调、日志)
  • 紧耦合(依赖4个服务)
  • 难以测试
  • 代码重复(每个方法都要注入所有服务)

步骤 3:重构后 - MediatR 架构 ​

1. 定义命令 ​

csharp
// Commands/CreateOrderCommand.cs
public class CreateOrderCommand : IRequest<OrderResult>
{
    public Guid CustomerId { get; set; }
    public List<OrderItemCommand> Items { get; set; }
    public string ShippingAddress { get; set; }
}

public class OrderItemCommand
{
    public Guid ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}

public class OrderResult
{
    public Guid OrderId { get; set; }
    public decimal TotalAmount { get; set; }
}

2. 实现处理器 ​

csharp
// Commands/CreateOrderHandler.cs
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepo;
    private readonly IPaymentService _paymentService;
    private readonly IInventoryService _inventoryService;
    private readonly IEmailService _emailService;
    private readonly IMediator _mediator;

    public CreateOrderHandler(
        IOrderRepository orderRepo,
        IPaymentService paymentService,
        IInventoryService inventoryService,
        IEmailService emailService,
        IMediator mediator)
    {
        _orderRepo = orderRepo;
        _paymentService = paymentService;
        _inventoryService = inventoryService;
        _emailService = emailService;
        _mediator = mediator;
    }

    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 1. 创建订单
        var order = new Order
        {
            Id = Guid.NewGuid(),
            CustomerId = request.CustomerId,
            Items = request.Items.Select(i => new OrderItem 
            { 
                ProductId = i.ProductId,
                Quantity = i.Quantity,
                Price = i.Price
            }).ToList(),
            ShippingAddress = request.ShippingAddress,
            CreatedAt = DateTime.UtcNow
        };

        await _orderRepo.AddAsync(order, ct);

        // 2. 处理支付
        await _paymentService.ProcessPayment(order.Id, ct);

        // 3. 更新库存
        await _inventoryService.UpdateStock(order.Items, ct);

        // 4. 发布事件(替代直接调用邮件服务)
        await _mediator.Publish(new OrderCreatedNotification
        {
            OrderId = order.Id,
            CustomerEmail = order.CustomerEmail,
            TotalAmount = order.TotalAmount
        }, ct);

        return new OrderResult
        {
            OrderId = order.Id,
            TotalAmount = order.TotalAmount
        };
    }
}

3. 简化控制器 ​

csharp
// Controllers/OrdersController.cs(重构后)
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;

    public OrdersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<ActionResult<OrderResult>> CreateOrder([FromBody] CreateOrderCommand command)
    {
        var result = await _mediator.Send(command);
        return CreatedAtAction(nameof(GetOrder), new { id = result.OrderId }, result);
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<OrderDto>> GetOrder(Guid id)
    {
        var query = new GetOrderQuery { OrderId = id };
        var order = await _mediator.Send(query);
        return Ok(order);
    }
}

改进:

  • ✅ 控制器只负责 HTTP 层面
  • ✅ 只依赖一个 IMediator
  • ✅ 易于测试
  • ✅ 代码简洁

步骤 4:添加管道行为 ​

日志行为 ​

csharp
// Behaviors/LoggingBehavior.cs
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public async Task<TResponse> Handle(
        TRequest request, 
        RequestHandlerDelegate<TResponse> next, 
        CancellationToken cancellationToken)
    {
        var requestName = typeof(TRequest).Name;
        _logger.LogInformation("处理请求: {RequestName}", requestName);

        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            var response = await next();
            stopwatch.Stop();
            
            _logger.LogInformation("请求完成: {RequestName}, 耗时: {ElapsedMilliseconds}ms", 
                requestName, stopwatch.ElapsedMilliseconds);
            
            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, "请求失败: {RequestName}, 耗时: {ElapsedMilliseconds}ms", 
                requestName, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

验证行为 ​

csharp
// Commands/CreateOrderValidator.cs
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(x => x.CustomerId)
            .NotEmpty().WithMessage("客户ID不能为空");

        RuleFor(x => x.Items)
            .NotEmpty().WithMessage("订单项不能为空")
            .Must(items => items.All(i => i.Quantity > 0))
            .WithMessage("商品数量必须大于0");

        RuleFor(x => x.ShippingAddress)
            .NotEmpty().WithMessage("收货地址不能为空");
    }
}

// Behaviors/ValidationBehavior.cs
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public async Task<TResponse> Handle(
        TRequest request, 
        RequestHandlerDelegate<TResponse> next, 
        CancellationToken cancellationToken)
    {
        if (_validators.Any())
        {
            var context = new ValidationContext<TRequest>(request);
            var results = await Task.WhenAll(
                _validators.Select(v => v.ValidateAsync(context, cancellationToken)));

            var failures = results.SelectMany(r => r.Errors).Where(f => f != null).ToList();

            if (failures.Count != 0)
                throw new ValidationException(failures);
        }

        return await next();
    }
}

注册行为 ​

csharp
// Program.cs
builder.Services.AddMediatR(cfg => 
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);

// 注册管道行为
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

步骤 5:使用通知解耦 ​

定义通知 ​

csharp
// Notifications/OrderCreatedNotification.cs
public class OrderCreatedNotification : INotification
{
    public Guid OrderId { get; set; }
    public string CustomerEmail { get; set; }
    public decimal TotalAmount { get; set; }
}

实现多个处理器 ​

csharp
// Notifications/SendEmailHandler.cs
public class SendEmailHandler : INotificationHandler<OrderCreatedNotification>
{
    private readonly IEmailService _emailService;

    public async Task Handle(OrderCreatedNotification notification, CancellationToken ct)
    {
        await _emailService.SendConfirmationAsync(notification.CustomerEmail);
    }
}

// Notifications/UpdateAnalyticsHandler.cs
public class UpdateAnalyticsHandler : INotificationHandler<OrderCreatedNotification>
{
    private readonly IAnalyticsService _analyticsService;

    public async Task Handle(OrderCreatedNotification notification, CancellationToken ct)
    {
        await _analyticsService.TrackEventAsync("order_created", new 
        { 
            notification.OrderId,
            notification.TotalAmount 
        });
    }
}

📊 改造前后对比 ​

代码对比 ​

维度改造前改造后改进
控制器依赖5个服务1个 IMediator✅ 减少 80%
控制器行数~50行~10行✅ 减少 80%
业务逻辑位置控制器Handler✅ 职责清晰
日志记录手动编写自动应用✅ DRY
参数验证分散在各处统一行为✅ 一致性
可扩展性修改控制器新增处理器✅ 开闭原则
单元测试复杂(Mock 5个服务)简单(Mock 1个)✅ 易测试

🎯 改造收益 ​

1. 代码质量提升 ​

csharp
// 改造前:50行
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
{
    _logger.LogInformation(...);
    if (request.Quantity <= 0) return BadRequest(...);
    var order = await _orderService.CreateOrder(request);
    await _paymentService.ProcessPayment(order.Id);
    await _inventoryService.UpdateStock(order.Items);
    await _emailService.SendConfirmation(order.CustomerEmail);
    _logger.LogInformation(...);
    return Ok(...);
}

// 改造后:3行
[HttpPost]
public async Task<ActionResult<OrderResult>> CreateOrder([FromBody] CreateOrderCommand command)
{
    var result = await _mediator.Send(command);
    return CreatedAtAction(nameof(GetOrder), new { id = result.OrderId }, result);
}

2. 可测试性提升 ​

csharp
// 改造前:需要 Mock 5个服务
[Fact]
public async Task CreateOrder_Success()
{
    var mockOrderService = new Mock<OrderService>();
    var mockPaymentService = new Mock<PaymentService>();
    var mockEmailService = new Mock<EmailService>();
    var mockInventoryService = new Mock<InventoryService>();
    var mockLogger = new Mock<ILogger>();
    
    var controller = new OrdersController(
        mockOrderService.Object,
        mockPaymentService.Object,
        mockEmailService.Object,
        mockInventoryService.Object,
        mockLogger.Object);
    
    // 测试复杂...
}

// 改造后:只需测试 Handler
[Fact]
public async Task CreateOrderHandler_Success()
{
    var mockRepo = new Mock<IOrderRepository>();
    var handler = new CreateOrderHandler(mockRepo.Object, ...);
    
    var result = await handler.Handle(new CreateOrderCommand { ... }, CancellationToken.None);
    
    Assert.NotNull(result);
    mockRepo.Verify(r => r.AddAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Once);
}

3. 扩展性提升 ​

csharp
// 需求:订单创建后增加发送短信通知

// 改造前:需要修改控制器
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
{
    await _smsService.SendSms(order.CustomerPhone); // 新增
    return Ok(...);
}

// 改造后:只需新增处理器,无需修改现有代码
public class SendSmsHandler : INotificationHandler<OrderCreatedNotification>
{
    public async Task Handle(OrderCreatedNotification notification, CancellationToken ct)
    {
        await _smsService.SendSmsAsync(notification.CustomerPhone);
    }
}

✅ 符合开闭原则:对扩展开放,对修改关闭


🎓 总结 ​

改造步骤回顾 ​

  1. ✅ 安装 MediatR NuGet 包
  2. ✅ 定义命令和查询
  3. ✅ 实现 Handler 迁移业务逻辑
  4. ✅ 简化控制器
  5. ✅ 添加管道行为(日志、验证)
  6. ✅ 使用通知解耦副作用

核心价值 ​

✅ 解耦: 控制器不依赖具体服务
✅ 简洁: 控制器代码减少 80%
✅ 一致: 统一的请求处理模式
✅ 易测: Handler 易于单元测试
✅ 扩展: 符合开闭原则


💡 提示: 从小模块开始重构,逐步推广到整个应用!

Released under the CC BY-SA 4.0 License.