如何将MediatR与业务层解耦?基于依赖倒置原则实现
用依赖倒置隔离MediatR,实现自定义领域事件的最佳方案
这个需求非常合理——通过依赖倒置隔离第三方库,能让你的领域层保持纯净,不会被具体的中间件绑定。下面是一套落地性很强的实现方案,我在几个项目里都用过,效果很好:
1. 定义自己的领域事件与处理器契约
首先在你的领域层(不依赖MediatR的项目)里,定义核心的接口,这是依赖倒置的基础:
// 自定义领域事件标记接口 public interface IMyProjectDomainEvent { } // 自定义领域事件处理器接口(支持异步) public interface IMyProjectDomainEventHandler<TEvent> where TEvent : IMyProjectDomainEvent { Task HandleAsync(TEvent domainEvent, CancellationToken cancellationToken = default); }
你的领域事件就可以像你期望的那样实现:
public class EmailConfirmedEvent : IMyProjectDomainEvent { public Guid PassengerId { get; } public string Email { get; } public EmailConfirmedEvent(Guid passengerId, string email) { Email = email; PassengerId = passengerId; } }
对应的自定义处理器也只依赖自己的接口:
public class EmailConfirmedEventHandler : IMyProjectDomainEventHandler<EmailConfirmedEvent> { public async Task HandleAsync(EmailConfirmedEvent domainEvent, CancellationToken cancellationToken) { // 处理逻辑,比如发送通知、更新状态等 await SendNotification(domainEvent.Email, cancellationToken); } private Task SendNotification(string email, CancellationToken ct) { // 具体实现 return Task.CompletedTask; } }
2. 实现MediatR适配器(Adapter模式)
接下来在基础设施层(可以依赖MediatR的项目)里,写两个通用适配器,把自定义的事件/处理器转换成MediatR能识别的类型:
事件适配器:把自定义事件包装成MediatR的INotification
public class MediatRDomainEventAdapter<TEvent> : INotification where TEvent : IMyProjectDomainEvent { public TEvent DomainEvent { get; } public MediatRDomainEventAdapter(TEvent domainEvent) { DomainEvent = domainEvent; } }
处理器适配器:把自定义处理器适配成MediatR的INotificationHandler
public class MediatRDomainEventHandlerAdapter<TEvent> : INotificationHandler<MediatRDomainEventAdapter<TEvent>> where TEvent : IMyProjectDomainEvent { private readonly IMyProjectDomainEventHandler<TEvent> _handler; public MediatRDomainEventHandlerAdapter(IMyProjectDomainEventHandler<TEvent> handler) { _handler = handler; } public async Task Handle(MediatRDomainEventAdapter<TEvent> notification, CancellationToken cancellationToken) { await _handler.HandleAsync(notification.DomainEvent, cancellationToken); } }
3. 依赖注入容器的注册逻辑
在你的启动项目(比如ASP.NET Core的Program.cs)里,需要注册三个核心部分:
- 所有自定义的领域事件处理器
- 所有MediatR的处理器适配器
- MediatR本身的服务
这里用批量扫描的方式,避免手动逐个注册:
var builder = WebApplication.CreateBuilder(args); // 1. 注册MediatR(指定包含适配器的程序集) builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining<MediatRDomainEventAdapter<IMyProjectDomainEvent>>(); }); // 2. 注册自定义领域事件处理器(扫描领域层程序集) builder.Services.Scan(scan => scan .FromAssemblyOf<IMyProjectDomainEvent>() .AddClasses(classes => classes.AssignableTo(typeof(IMyProjectDomainEventHandler<>))) .AsImplementedInterfaces() .WithTransientLifetime()); // 3. 注册MediatR处理器适配器(扫描基础设施层程序集) builder.Services.Scan(scan => scan .FromAssemblyOf<MediatRDomainEventHandlerAdapter<IMyProjectDomainEvent>>() .AddClasses(classes => classes.AssignableTo(typeof(INotificationHandler<>))) .AsImplementedInterfaces() .WithTransientLifetime());
4. 封装事件发布器,彻底隐藏MediatR
最后,为了让业务代码完全不接触MediatR的IMediator接口,我们封装自己的发布器:
// 自定义发布器接口(领域层引用) public interface IDomainEventPublisher { Task PublishAsync<TEvent>(TEvent domainEvent, CancellationToken cancellationToken = default) where TEvent : IMyProjectDomainEvent; } // 基础设施层的实现,依赖MediatR的IMediator public class MediatRDomainEventPublisher : IDomainEventPublisher { private readonly IMediator _mediator; public MediatRDomainEventPublisher(IMediator mediator) { _mediator = mediator; } public async Task PublishAsync<TEvent>(TEvent domainEvent, CancellationToken cancellationToken = default) where TEvent : IMyProjectDomainEvent { // 把自定义事件包装成MediatR的通知,然后发布 await _mediator.Publish(new MediatRDomainEventAdapter<TEvent>(domainEvent), cancellationToken); } }
然后注册这个发布器:
builder.Services.AddTransient<IDomainEventPublisher, MediatRDomainEventPublisher>();
5. 业务代码中使用
现在你的业务逻辑只需要依赖自己的IDomainEventPublisher和领域事件,完全看不到MediatR的痕迹:
public class PassengerService { private readonly IDomainEventPublisher _eventPublisher; public PassengerService(IDomainEventPublisher eventPublisher) { _eventPublisher = eventPublisher; } public async Task ConfirmEmailAsync(Guid passengerId, string email) { // 业务逻辑... // 发布领域事件 await _eventPublisher.PublishAsync(new EmailConfirmedEvent(passengerId, email)); } }
额外注意点
- 如果你的处理器有同步需求,可以给
IMyProjectDomainEventHandler加一个同步的Handle方法,然后在适配器里做兼容 - 如果你用的是其他DI容器(比如Autofac),调整注册逻辑即可,核心思路不变
- 可以考虑给领域事件加一个
DateTime OccurredOn属性,记录事件发生时间,这是领域事件的常见实践
内容的提问来源于stack exchange,提问作者Henrick Kakutalua
相关产品推荐
相关产品推荐

