You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:57:41