11

我刚刚开始使用 DDD。我正在将域事件放入 CQRS 应用程序中,并且遇到了一项基本任务:如何在域项目中使用 MediatR.INotification 标记接口而不创建对基础结构的域依赖。

我的解决方案分为四个项目,如下所示:

MyApp.Domain
    - Domain events
    - Aggregates
    - Interfaces (IRepository, etc), etc.
MyApp.ApplicationServices
    - Commands
    - Command Handlers, etc.
MyApp.Infrastructure
    - Repository
    - Emailer, etc.
MyApp.Web
    - Startup
    - MediatR NuGet packages and DI here
    - UI, etc.

我目前在 UI 项目中安装了 MediatR 和 MediatR .net Core DI 包,并使用 .AddMediatR() 使用命令将它们添加到 DI

services.AddMediatR(typeof(MyApp.AppServices.Commands.Command).Assembly);

它从 AppServices 项目中扫描并注册命令处理程序。

当我想定义一个事件时,问题就来了。为了让 MediatR 处理我的域事件,它们需要使用 MediatR.INotification 接口进行标记。

namespace ObApp.Domain.Events
{
    public class NewUserAdded : INotification
    {
        ...
    }

在这种情况下标记我的事件以便 MediatR 可以使用它们的正确方法是什么?我可以为事件创建自己的标记界面,但如果没有某种方式将它们自动转换为 MediatR.INotification,MediatR 将无法识别它们。

这只是使用多个项目的缺点吗?但是,即使我使用的是单个项目,如果我在域部分中使用 MediatR.INotification,我也会在域中放置一个“外部”接口。

当我的用户实体从 EF 的 IdentityUser 继承时,我遇到了同样的问题。在那种情况下,网络共识似乎说是务实的,继续前进,让轻微的污染可以省去很多麻烦。这是另一个类似的案例吗?我不介意为了实用主义而牺牲纯洁,但不仅仅是为了懒惰。

这是我使用的其他软件包会出现的一个基本问题,所以我期待解决这个问题。

谢谢!

4

6 回答 6

11

最好您的域层不依赖于任何基础设施,但由于绑定,这在 CQRS 中很难获得。我可以从我的经验告诉你。但是,您可以最小化这种依赖性。一种方法是制作自己的,在整个域代码EventInterface中扩展和使用该接口。MediatR.INotification这样,如果你想改变基础设施,你只需要改变一个地方。

于 2017-11-14T20:29:44.793 回答
4

如果您想保持您的领域层真正纯净,而不需要任何对 MediatR 的引用,请在领域层中为事件、中介和处理程序创建自己的接口。然后在基础设施或应用层,创建包装类来包装 MediatR 并通过包装类传递调用。使用这种方法,您不需要从 MediatR 接口派生。确保在你的 IoC 中注册包装器

这是一个例子:

在您的域层中:

public interface IDomainMediator
{
    Task Publish<TNotification>(TNotification notification,
        CancellationToken cancellationToken = default(CancellationToken))
        where TNotification : IDomainNotification;
}
public interface IDomainNotification
{}
public interface IDomainNotificationHandler<in TNotification>
    where TNotification : IDomainNotification
{
    Task Handle(TNotification notification, 
        CancellationToken cancellationToken=default(CancellationToken));
}

然后在您的基础设施或应用程序层中,无论您拥有 MediatR 包:

public class MediatRWrapper : IDomainMediator
{
    private readonly MediatR.IMediator _mediator;

    public MediatRWrapper(MediatR.IMediator mediator)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
    }

    public Task Publish<TNotification>(TNotification notification,
        CancellationToken cancellationToken = default(CancellationToken))
        where TNotification : IDomainNotification
    {
        var notification2 = new NotificationWrapper<TNotification>(notification);
        return _mediator.Publish(notification2, cancellationToken);
    }
}

public class NotificationWrapper<T> : MediatR.INotification
{
    public T Notification { get; }

    public NotificationWrapper(T notification)
    {
        Notification = notification;
    }
}

public class NotificationHandlerWrapper<T1, T2> : MediatR.INotificationHandler<T1>
    where T1 : NotificationWrapper<T2>
    where T2 : IDomainNotification
{
    private readonly IEnumerable<IDomainNotificationHandler<T2>> _handlers;

    //the IoC should inject all domain handlers here
    public NotificationHandlerWrapper(
           IEnumerable<IDomainNotificationHandler<T2>> handlers)
    {
        _handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
    }

    public Task Handle(T1 notification, CancellationToken cancellationToken)
    {
        var handlingTasks = _handlers.Select(h => 
          h.Handle(notification.Notification, cancellationToken));
        return Task.WhenAll(handlingTasks);
    }
}

我没有用管道等测试过它,但它应该可以工作。干杯!

于 2018-03-12T08:39:23.343 回答
3

尝试首先在域层中没有基础设施依赖将是一等奖。

我不知道 MediatR,但根据您的描述,它需要在将在该空间中使用的类上实现一个接口。

是否可以选择创建一个位于您的域之外的包装类?

public class MediatRNotification<T> : INotification
{
    T Instance { get; }

    public MediatRNotification(T instance)
    {
        Instance = instance;
    }
}

您的基础设施甚至可以使用一些反射从域事件中创建此包装器。

于 2017-11-15T05:43:23.993 回答
1

如果您想利用 mediatR 多态性进行通知而不使用 MediatR.INotification 派生您的域事件,请按照 Eben 的说明创建一个包装器。

public class DomainEventNotification<TDomainEvent> : INotification where TDomainEvent : IDomainEvent
{
    public TDomainEvent DomainEvent { get; }

    public DomainEventNotification(TDomainEvent domainEvent)
    {
        DomainEvent = domainEvent;
    }
}

然后通过应用动态来使用正确的类型而不是域事件接口创建它。更多解释请看这篇文章

public class DomainEventDispatcher : IDomainEventChangesConsumer
{
    private readonly IMediator _mediator;

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

    public void Consume(IAggregateId aggregateId, IReadOnlyList<IDomainEvent> changes)
    {
        foreach (var change in changes)
        {
            var domainEventNotification = CreateDomainEventNotification((dynamic)change);

            _mediator.Publish(domainEventNotification);
        }
    }

    private static DomainEventNotification<TDomainEvent> CreateDomainEventNotification<TDomainEvent>(TDomainEvent domainEvent) 
        where TDomainEvent : IDomainEvent
    {
        return new DomainEventNotification<TDomainEvent>(domainEvent);
    }
}

您的域事件类型的处理程序将被调用:

public class YourDomainEventHandler
    : INotificationHandler<DomainEventNotification<YourDomainEvent>>
{
    public Task Handle(DomainEventNotification<YourDomainEvent> notification, CancellationToken cancellationToken)
    {
        // Handle your domain event
    }
}

public class YourDomainEvent : IDomainEvent
{
    // Your domain event ...
}
于 2018-05-28T08:56:28.937 回答
0

这是一种无需使用基础架构接口即可使用的方法 https://github.com/Leanwit/dotnet-cqrs

从 GitHub 网站:

这个项目展示了一种在不使用 MediatR 库的情况下使用 CQRS 的简洁方法。

在 C# 中,通常使用名为 MediatR 的库来实现 CQRS。这是一个了不起的库,但它迫使您在域/应用程序层中实现接口 INotification、INotificationHandler 和 IRequestHandler,并将其与基础设施库相结合。这是避免添加这种耦合的另一种方法。

于 2020-05-22T22:59:14.177 回答
0

正如其他人所提到的,共识似乎是 wrap MediatR.INotification。我发现这篇 2020 年的帖子非常有用。

我们确实必须处理我们的领域事件不是有效的 MediatR INotification 的小问题。我们将通过创建一个通用的 INotification 来包装我们的领域事件来克服这个问题。

创建自定义通用 INotification。

using System;
using MediatR;
using DomainEventsMediatR.Domain;

namespace DomainEventsMediatR.Application
{
    public class DomainEventNotification<TDomainEvent> : INotification where TDomainEvent : IDomainEvent
    {
        public TDomainEvent DomainEvent { get; }

        public DomainEventNotification(TDomainEvent domainEvent)
        {
            DomainEvent = domainEvent;
        }
    }
}

创建一个 Dispatcher,在 MediatR 通知中包装域事件并发布它们:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using MediatR;
using DomainEventsMediatR.Domain;

namespace DomainEventsMediatR.Application
{
    public class MediatrDomainEventDispatcher : IDomainEventDispatcher
    {
        private readonly IMediator _mediator;
        private readonly ILogger<MediatrDomainEventDispatcher> _log;
        public MediatrDomainEventDispatcher(IMediator mediator, ILogger<MediatrDomainEventDispatcher> log)
        {
            _mediator = mediator;
            _log = log;
        }

        public async Task Dispatch(IDomainEvent devent)
        {

            var domainEventNotification = _createDomainEventNotification(devent);
            _log.LogDebug("Dispatching Domain Event as MediatR notification.  EventType: {eventType}", devent.GetType());
            await _mediator.Publish(domainEventNotification);
        }
       
        private INotification _createDomainEventNotification(IDomainEvent domainEvent)
        {
            var genericDispatcherType = typeof(DomainEventNotification<>).MakeGenericType(domainEvent.GetType());
            return (INotification)Activator.CreateInstance(genericDispatcherType, domainEvent);

        }
    }
}

微软的方法

请注意,在其CQRS 完整示例中,Microsoft建议在域实体中简单地引用 MediatR 接口:

在 C# 中,域事件只是一个数据保存结构或类,如 DTO,其中包含与域中刚刚发生的事情相关的所有信息,如下例所示:

public class OrderStartedDomainEvent : INotification
{
    public string UserId { get; }
    public string UserName { get; }
    public int CardTypeId { get; }
    public string CardNumber { get; }
    public string CardSecurityNumber { get; }
    public string CardHolderName { get; }
    public DateTime CardExpiration { get; }
    public Order Order { get; }

    public OrderStartedDomainEvent(Order order, string userId, string userName,
                                   int cardTypeId, string cardNumber,
                                   string cardSecurityNumber, string cardHolderName,
                                   DateTime cardExpiration)
    {
        Order = order;
        UserId = userId;
        UserName = userName;
        CardTypeId = cardTypeId;
        CardNumber = cardNumber;
        CardSecurityNumber = cardSecurityNumber;
        CardHolderName = cardHolderName;
        CardExpiration = cardExpiration;
    }
}

首先,您将实体中发生的事件添加到每个实体的事件集合或列表中。该列表应该是实体对象的一部分,或者更好的是,您的基实体类的一部分,如以下 Entity 基类示例所示:

public abstract class Entity
{
     //...
     private List<INotification> _domainEvents;
     public List<INotification> DomainEvents => _domainEvents;

     public void AddDomainEvent(INotification eventItem)
     {
         _domainEvents = _domainEvents ?? new List<INotification>();
         _domainEvents.Add(eventItem);
     }

     public void RemoveDomainEvent(INotification eventItem)
     {
         _domainEvents?.Remove(eventItem);
     }
     //... Additional code
}
于 2021-11-18T12:24:30.060 回答