DDD:从域项目

时间:2017-11-14 18:33:18

标签: c# .net domain-driven-design mediatr

我刚刚开始使用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将无法识别那些没有某种方法自动将它们转换为MediatR.INotification。

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

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

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

谢谢!

5 个答案:

答案 0 :(得分:4)

您的域层最好不依赖于任何基础架构,但由于绑定,很难在CQRS中获取。我可以从我的经历告诉你。但是,您可以最小化该依赖性。一种方法是创建扩展EventInterface的自己的MediatR.INotification并在整个域代码中使用该接口。通过这种方式,如果您想要更改基础架构,则只需在一个位置进行更改。

答案 1 :(得分:2)

尝试首先在域层中具有基础架构依赖性将是第一奖。

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

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

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

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

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

答案 2 :(得分:2)

如果您希望保持域图层真正纯净,而不引用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);
    }
}

我还没有用管道等测试它,但它应该可行。 干杯!

答案 3 :(得分:2)

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

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

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

然后通过应用动态来使用正确的类型而不是域事件接口创建它。有关详细说明,请参阅this article

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 ...
}

答案 4 :(得分:0)

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

在GitHub站点上:

此项目显示了不使用MediatR即可使用CQRS的干净方法 库。

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