起初,MediatR 如同魔法一般。
它解耦了一切。不再有服务之间的面条式耦合。只有整洁的 IMediator.Send()
调用,像行为良好的无人机一样穿梭于我们的处理程序之间。
有一段时间,它工作得很好——尤其是在我们早期的清洁架构(Clean Architecture)实验中。
但在我们开发第三个微服务和经历第十次生产事故之间的某个时候,那些整洁的代码变成了一场调试噩梦。
当抽象发生泄漏,日志随之消亡 事情是这样的。
我们正在扩展一个拥有数十个松散耦合微服务的系统。每个服务内部使用 MediatR 来处理命令(Command)和查询(Query)。初衷是高尚的:关注点分离、可测试性,以及无处不在的单一职责原则。
但抽象变成了一种蒙蔽。 当用户在前端点击“提交”而请求在半路失败时,我们完全不知道原因。
调用栈?被混淆了。 日志?分散各处。 错误?被埋藏在三层处理程序之深。
我们的一位开发人员——暂且叫他 Raj——花了六个小时追踪一个空引用(null ref)bug,这个 bug 被一个预处理程序(pre-handler)默默地吞掉了。结果发现管道注入的作用域服务与预期不同。没有异常。没有提示。只有一片寂静。
那时我们明白了:出问题了。
这不仅仅是调试的痛苦。以下是 MediatR 对我们的项目造成的真实影响:
Send()
都会触发一连串的行为(Behavior)——日志记录、验证、授权——即使对于简单的查询也是如此。在热点路径上(如获取用户仪表板的公共 API),这会快速累积。对一个端点进行重构前后的基准测试:
// 使用 MediatR (管道行为 + 处理程序)
var result = await _mediator.Send(new GetUserDashboardQuery(userId));
// 重构后 (直接服务调用)
var result = await _dashboardService.GetUserDashboardAsync(userId);
| 方法 | 平均响应时间 | | :------------- | :----------- | | MediatR | 118ms | | 直接服务调用 | 52ms |
仅仅是抽象开销,就慢了超过 2 倍。
我们曾经有这样的测试:
var mediator = new Mock<IMediator>();
mediator.Setup(m => m.Send(It.IsAny<MyCommand>(), default))
.ReturnsAsync(new MyResult());
var sut = new MyController(mediator.Object);
// 执行操作 (Act) + 断言 (Assert)
弃用 MediatR 后:
var dashboardService = new Mock<IDashboardService>();
dashboardService.Setup(s => s.GetUserDashboardAsync(userId))
.ReturnsAsync(new DashboardData());
var sut = new DashboardController(dashboardService.Object);
// 执行操作 (Act) + 断言 (Assert)
更简单。更可读。更少的模拟。我们终于是在测试行为,而不是管道代码。
一位初级开发人员说得很好:
“感觉就像我在玩‘你丢我捡’(fetch)游戏来进行调试。”
我们转向了:更简单的事件驱动模式 我们不再默认在所有地方使用 MediatR,而是采用了一种混合的事件驱动模式,它结合了服务层调用和领域事件(Domain Event)。以下是转变:
我们实现了一个轻量级的领域事件分发器,如下所示:
public interface IDomainEvent { }
public interface IEventHandler<T> where T : IDomainEvent
{
Task HandleAsync(T @event);
}
事件在工作单元(Unit of Work)提交后发布:
public class User
{
public void Activate()
{
IsActive = true;
AddDomainEvent(new UserActivatedEvent(Id));
}
}
处理程序保持独立,即发即忘(fire-and-forget):
public class SendWelcomeEmailHandler : IEventHandler<UserActivatedEvent>
{
public Task HandleAsync(UserActivatedEvent @event)
{
return _emailService.SendWelcomeEmailAsync(@event.UserId);
}
}
这是显式的。可追踪的。代码读起来像一个故事——而不是一本悬疑小说。
MediatR 在哪些场景下仍然适用 让我澄清:MediatR 并不坏。 它只是不再是我们默认的选择。
我们仍然在以下场景中使用它:
但是对于大型的、生产级别的系统? 我们更倾向于显性化而非纯粹性。
最后的思考:过度抽象的代价 清洁架构的拥趸们喜欢 MediatR,因为它满足了所有条件:SOLID、DDD、CQRS。但如果你不小心,你会构建一个完全解耦的系统——从现实中解耦。
如果你的目标是可维护、可观测和高性能的软件,问问自己:
我这里真的需要一个中介者(Mediator)吗? 或者我是在解决一个尚未存在的问题?
在我们的案例中,移除 MediatR 不仅仅是清理了架构——它还明确了所有权、降低了延迟,并让开发人员更开心。
这比教科书上的图表更有价值。
欢迎鼓掌(Claps)。特别是如果你曾经愤怒地调试过管道行为(pipeline behaviour)。 我们来聊聊:你还在使用 MediatR 吗?你会停止使用吗?
完整代码示例 下面的示例演示了本文中关于放弃 MediatR 的关键点。这将通过实际示例展示“之前”和“之后”的方法。
// =============================================================================
// 之前: 使用 MediatR 模式
// =============================================================================
// 命令/查询定义
public class GetUserDashboardQuery : IRequest<DashboardData>
{
public int UserId { get; set; }
}
public class ActivateUserCommand : IRequest<bool>
{
public int UserId { get; set; }
}
// MediatR 处理程序
public class GetUserDashboardHandler : IRequestHandler<GetUserDashboardQuery, DashboardData>
{
private readonly IUserRepository _userRepository;
private readonly IDashboardService _dashboardService;
public GetUserDashboardHandler(IUserRepository userRepository, IDashboardService dashboardService)
{
_userRepository = userRepository;
_dashboardService = dashboardService;
}
public async Task<DashboardData> Handle(GetUserDashboardQuery request, CancellationToken cancellationToken)
{
var user = await _userRepository.GetByIdAsync(request.UserId);
if (user == null) throw new UserNotFoundException();
return await _dashboardService.BuildDashboardAsync(user);
}
}
public class ActivateUserHandler : IRequestHandler<ActivateUserCommand, bool>
{
private readonly IUserRepository _userRepository;
private readonly IEmailService _emailService;
public ActivateUserHandler(IUserRepository userRepository, IEmailService emailService)
{
_userRepository = userRepository;
_emailService = emailService;
}
public async Task<bool> Handle(ActivateUserCommand request, CancellationToken cancellationToken)
{
var user = await _userRepository.GetByIdAsync(request.UserId);
if (user == null) return false;
user.Activate();
await _userRepository.UpdateAsync(user);
// 副作用处理内联
await _emailService.SendWelcomeEmailAsync(user.Id);
return true;
}
}
// 管道行为 (增加了复杂性)
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 cancellationToken)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
var response = await next();
_logger.LogInformation($"Handled {typeof(TRequest).Name}");
return response;
}
}
// 使用 MediatR 的控制器
[ApiController]
[Route("[controller]")]
public class DashboardController : ControllerBase
{
private readonly IMediator _mediator;
public DashboardController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet("{userId}")]
public async Task<DashboardData> GetUserDashboard(int userId)
{
return await _mediator.Send(new GetUserDashboardQuery { UserId = userId });
}
[HttpPost("{userId}/activate")]
public async Task<bool> ActivateUser(int userId)
{
return await _mediator.Send(new ActivateUserCommand { UserId = userId });
}
}
// 使用 MediatR 的复杂测试设置
[Test]
public async Task GetUserDashboard_ShouldReturnDashboard_WhenUserExists()
{
// 安排 (Arrange)
var mediator = new Mock<IMediator>();
var expectedDashboard = new DashboardData { UserId = 1, Name = "Test User" };
mediator.Setup(m => m.Send(It.IsAny<GetUserDashboardQuery>(), default))
.ReturnsAsync(expectedDashboard);
var controller = new DashboardController(mediator.Object);
// 执行 (Act)
var result = await controller.GetUserDashboard(1);
// 断言 (Assert)
Assert.AreEqual(expectedDashboard, result);
mediator.Verify(m => m.Send(It.IsAny<GetUserDashboardQuery>(), default), Times.Once);
}
// =============================================================================
// 之后: 直接服务调用与领域事件方法
// =============================================================================
// 领域事件
public interface IDomainEvent { }
public class UserActivatedEvent : IDomainEvent
{
public int UserId { get; }
public DateTime OccurredAt { get; }
public UserActivatedEvent(int userId)
{
UserId = userId;
OccurredAt = DateTime.UtcNow;
}
}
// 事件处理程序接口
public interface IEventHandler<T> where T : IDomainEvent
{
Task HandleAsync(T @event);
}
// 领域事件分发器
public interface IDomainEventDispatcher
{
Task DispatchAsync<T>(T @event) where T : IDomainEvent;
}
public class DomainEventDispatcher : IDomainEventDispatcher
{
private readonly IServiceProvider _serviceProvider;
public DomainEventDispatcher(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task DispatchAsync<T>(T @event) where T : IDomainEvent
{
var handlers = _serviceProvider.GetServices<IEventHandler<T>>();
var tasks = handlers.Select(handler => handler.HandleAsync(@event));
await Task.WhenAll(tasks);
}
}
// 增强的领域实体 (包含事件)
public class User
{
private readonly List<IDomainEvent> _domainEvents = new();
public int Id { get; private set; }
public string Name { get; private set; }
public bool IsActive { get; private set; }
public void Activate()
{
if (!IsActive)
{
IsActive = true;
AddDomainEvent(new UserActivatedEvent(Id));
}
}
public void AddDomainEvent(IDomainEvent @event)
{
_domainEvents.Add(@event);
}
public IReadOnlyList<IDomainEvent> GetDomainEvents() => _domainEvents.AsReadOnly();
public void ClearDomainEvents() => _domainEvents.Clear();
}
// 应用服务 (直接调用,无 MediatR)
public interface IUserService
{
Task<DashboardData> GetUserDashboardAsync(int userId);
Task<bool> ActivateUserAsync(int userId);
}
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IDashboardService _dashboardService;
private readonly IDomainEventDispatcher _eventDispatcher;
public UserService(
IUserRepository userRepository,
IDashboardService dashboardService,
IDomainEventDispatcher eventDispatcher)
{
_userRepository = userRepository;
_dashboardService = dashboardService;
_eventDispatcher = eventDispatcher;
}
public async Task<DashboardData> GetUserDashboardAsync(int userId)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null) throw new UserNotFoundException();
return await _dashboardService.BuildDashboardAsync(user);
}
public async Task<bool> ActivateUserAsync(int userId)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null) return false;
user.Activate();
await _userRepository.UpdateAsync(user);
// 在成功持久化后分发领域事件
var events = user.GetDomainEvents();
foreach (var @event in events)
{
await _eventDispatcher.DispatchAsync(@event);
}
user.ClearDomainEvents();
return true;
}
}
// 事件处理程序 (分离的副作用)
public class SendWelcomeEmailHandler : IEventHandler<UserActivatedEvent>
{
private readonly IEmailService _emailService;
private readonly ILogger<SendWelcomeEmailHandler> _logger;
public SendWelcomeEmailHandler(IEmailService emailService, ILogger<SendWelcomeEmailHandler> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task HandleAsync(UserActivatedEvent @event)
{
_logger.LogInformation($"Sending welcome email to user {@event.UserId}");
await _emailService.SendWelcomeEmailAsync(@event.UserId);
}
}
public class UpdateUserStatsHandler : IEventHandler<UserActivatedEvent>
{
private readonly IUserStatsService _userStatsService;
public UpdateUserStatsHandler(IUserStatsService userStatsService)
{
_userStatsService = userStatsService;
}
public async Task HandleAsync(UserActivatedEvent @event)
{
await _userStatsService.IncrementActiveUsersAsync();
}
}
// 简化后的控制器 (直接服务调用)
[ApiController]
[Route("[controller]")]
public class DashboardController : ControllerBase
{
private readonly IUserService _userService;
public DashboardController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{userId}")]
public async Task<DashboardData> GetUserDashboard(int userId)
{
return await _userService.GetUserDashboardAsync(userId);
}
[HttpPost("{userId}/activate")]
public async Task<bool> ActivateUser(int userId)
{
return await _userService.ActivateUserAsync(userId);
}
}
// 简化后的测试设置
[Test]
public async Task GetUserDashboard_ShouldReturnDashboard_WhenUserExists()
{
// 安排 (Arrange)
var userService = new Mock<IUserService>();
var expectedDashboard = new DashboardData { UserId = 1, Name = "Test User" };
userService.Setup(s => s.GetUserDashboardAsync(1))
.ReturnsAsync(expectedDashboard);
var controller = new DashboardController(userService.Object);
// 执行 (Act)
var result = await controller.GetUserDashboard(1);
// 断言 (Assert)
Assert.AreEqual(expectedDashboard, result);
userService.Verify(s => s.GetUserDashboardAsync(1), Times.Once);
}
// =============================================================================
// 支撑类和接口
// =============================================================================
public class DashboardData
{
public int UserId { get; set; }
public string Name { get; set; }
public List<string> RecentActivities { get; set; } = new();
}
public interface IUserRepository
{
Task<User> GetByIdAsync(int id);
Task UpdateAsync(User user);
}
public interface IDashboardService
{
Task<DashboardData> BuildDashboardAsync(User user);
}
public interface IEmailService
{
Task SendWelcomeEmailAsync(int userId);
}
public interface IUserStatsService
{
Task IncrementActiveUsersAsync();
}
public class UserNotFoundException : Exception
{
public UserNotFoundException() : base("User not found") { }
}
// =============================================================================
// 依赖注入容器设置对比
// =============================================================================
// MediatR 设置 (Startup.cs)
public void ConfigureServicesMediatR(IServiceCollection services)
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
// ... 其他管道行为
}
// 直接服务设置 (Startup.cs)
public void ConfigureServicesDirect(IServiceCollection services)
{
services.AddScoped<IUserService, UserService>();
services.AddScoped<IDomainEventDispatcher, DomainEventDispatcher>();
services.AddScoped<IEventHandler<UserActivatedEvent>, SendWelcomeEmailHandler>();
services.AddScoped<IEventHandler<UserActivatedEvent>, UpdateUserStatsHandler>();
// ... 其他服务
}