使用DDD模式实现简单API(8/5)

发布和处理领域事件

介绍

领域事件是领域驱动模式的基础,在领域执行时产生,并持有一部分数据。创建领域事件是为了通知领域内的其他部分,告诉它有事情发生了,一般情况下,对方会对此做出响应。

领域事件一般是以过去式命名的不可变的数据容器:

public class OrderPlaced
{
public Order Order { get; }

public OrderPlaced(Order order)
{
this.Order = order;
}
}

三种发布领域事件的方法

使用静态的DomainEvents类

简而言之,创建一个名为DomainEvents的静态类,包含Raise方法,当集成根内的事件发生时,立即调用方法。
这里需要注意的是,这会导致事件的处理器也会被立即调用,而此时集成根的内部方法可能还未结束。

public class OrderPlaced
{
public Order Order { get; }

public OrderPlaced(Order order)
{
this.Order = order;
}
}

由集成根内部返回相应的事件,然后在命令处理器发布

这种方法中,集成根会将领域事件返回给它的调用者,然后由他去发布事件:

public List PlaceOrder(Order order)
{
// business logic....

List events = new List();
events.Add(new OrderPlaced(order));

return events;
}
public class ShopApplicationService
{
private readonly IOrderRepository orderRepository;
private readonly IShopRepository shopRepository;
private readonly IEventsPublisher eventsPublisher;

public void PlaceOrder(int shopId, int orderId)
{
Shop shop = this.shopRepository.GetById(shopId);
Order order = this.orderRepository.GetById(orderId);

List events = shop.PlaceOrder(order);

eventsPublisher.Publish(events);
}
}

添加事件到Events实体集合

这种方式下,每个实体都拥有一个Events集合。当集成根的方法执行时,领域事件的实例被添加到集合中。当方法执行完成后,
调用者会检查所有的实体,获取领域事件然后发布他们。

public abstract class EntityBase
{
ICollection Events { get; }
}

public class Shop : EntityBase
{
public List PlaceOrder(Order order)
{
// business logic....

Events.Add(new OrderPlaced(order));
}
}
public class ShopApplicationService
{
private readonly IOrderRepository orderRepository;
private readonly IShopRepository shopRepository;
private readonly IEventsPublisher eventsPublisher;

public void PlaceOrder(int shopId, int orderId)
{
Shop shop = this.shopRepository.GetById(shopId);
Order order = this.orderRepository.GetById(orderId);

shop.PlaceOrder(order);

eventsPublisher.Publish();
}
}
public class EventsPublisher : IEventsPublisher
{
public void Publish()
{
List events = this.GetEvents(); // for example from EF DbContext

foreach (IDomainEvent @event in events)
{
this.Publish(@event);
}
}
}

处理领域事件

处理领域事件的方法直接取决于发布的方式,如果使用DomainEvents静态类,那么就需要立即处理事件。
在另外两种情况下,则可以控制事件何时发布以及何时处理,在事务之内或事务外。

在我看来,应该将领域事件的处理作为事务的一部分,将集成根方法的执行和领域事件的处理视为原子操作。
那么当有大量的事件和事件处理器时,不需要一一去考虑初始化数据库连接、事务的问题。
然后,有时需要与第三方的服务进行交互(例如邮件服务或webservice),在这种场景下是无法保持事务的,所以我们需要其他的机制来处理这种情况,也就是Domain Events Notifications。

领域事件通知

在领域驱动设计中是没有这个术语的,是我对它进行了命名,我觉得它很合适-它通知了领域事件被发布这件事。
整个机制是非常简单的,当我想要通知应用有领域事件被发布时,我会创一个对应的通知类,以及任意个对应通知类的
通知处理器。通知的发布是在事务提交之后进行的,整个流程如下:

  1. 创建数据库事务
  2. 获得集成根
  3. 调用集成根方法
  4. 添加领域事件到事件集合
  5. 发布领域事件并处理
  6. 保存修改到数据库并提交事务
  7. 发布领域事件通知并处理

我如何知道哪个领域事件被发布了呢?
首先需要根据领域事件的类型定义通知:

public interface IDomainEventNotification where TEventType:IDomainEvent
{
TEventType DomainEvent { get; }
}

public class DomainNotificationBase : IDomainEventNotification where T:IDomainEvent
{
public T DomainEvent { get; }

public DomainNotificationBase(T domainEvent)
{
this.DomainEvent = domainEvent;
}
}

public class OrderPlacedNotification : DomainNotificationBase
{
public OrderPlacedNotification(OrderPlaced orderPlaced) : base(domainEvent)
{
}
}

将所有的通知在IOC容器注册:

protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(IDomainEvent).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IDomainEventNotification<>));
}

在EventsPublisher中,从IOC容器获取通知的实例,在事务提交之后,所有的通知将被发布:

var domainEventNotifications = new List<IDomainEventNotification>();
foreach (var domainEvent in domainEvents)
{
Type domainEvenNotificationType = typeof(IDomainEventNotification<>);
var domainNotificationWithGenericType = domainEvenNotificationType.MakeGenericType(domainEvent.GetType());
var domainNotification = _scope.ResolveOptional(domainNotificationWithGenericType, new List
{
new NamedParameter("domainEvent", domainEvent)
});

if (domainNotification != null)
{
domainEventNotifications.Add(domainNotification as IDomainEventNotification);
}
}

var tasks = domainEventNotifications
.Select(async (notification) =>
{
await _mediator.Publish(notification, cancellationToken);
});

await Task.WhenAll(tasks);

下图显示了整个流程是如何执行的:

你可能会觉得有许多内容需要记忆,确实如此!但是实际上整个流程还是十分简单直接的,并且我们可以使用IOC拦截器来简化这个流程,
在后面的文章会说到。


总结

  • 领域事件是集成根中发生的过去的某件事,这是领域驱动模式中非常重要的一个概念。
  • 有多种发布和处理领域事件的方法-静态类、返回事件对象、通过集合暴露
  • 领域事件应该在事务内处理
  • 对于非事务内的操作,通过领域事件通知完成
posted @ 2020-03-03 09:53  东方未  阅读(150)  评论(0)    收藏  举报