使用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<IDomainEvent> PlaceOrder(Order order)
{
// business logic....
List<IDomainEvent> events = new List<IDomainEvent>();
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<IDomainEvent> events = shop.PlaceOrder(order);
eventsPublisher.Publish(events);
}
}
添加事件到Events实体集合
这种方式下,每个实体都拥有一个Events集合。当集成根的方法执行时,领域事件的实例被添加到集合中。当方法执行完成后,
调用者会检查所有的实体,获取领域事件然后发布他们。
public abstract class EntityBase
{
ICollection<IDomainEvent> Events { get; }
}
public class Shop : EntityBase
{
public List<IDomainEvent> 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<IDomainEvent> events = this.GetEvents(); // for example from EF DbContext
foreach (IDomainEvent @event in events)
{
this.Publish(@event);
}
}
}
处理领域事件
处理领域事件的方法直接取决于发布的方式,如果使用DomainEvents静态类,那么就需要立即处理事件。
在另外两种情况下,则可以控制事件何时发布以及何时处理,在事务之内或事务外。

在我看来,应该将领域事件的处理作为事务的一部分,将集成根方法的执行和领域事件的处理视为原子操作。
那么当有大量的事件和事件处理器时,不需要一一去考虑初始化数据库连接、事务的问题。
然后,有时需要与第三方的服务进行交互(例如邮件服务或webservice),在这种场景下是无法保持事务的,所以我们需要其他的机制来处理这种情况,也就是Domain Events Notifications。
领域事件通知
在领域驱动设计中是没有这个术语的,是我对它进行了命名,我觉得它很合适-它通知了领域事件被发布这件事。
整个机制是非常简单的,当我想要通知应用有领域事件被发布时,我会创一个对应的通知类,以及任意个对应通知类的
通知处理器。通知的发布是在事务提交之后进行的,整个流程如下:
- 创建数据库事务
- 获得集成根
- 调用集成根方法
- 添加领域事件到事件集合
- 发布领域事件并处理
- 保存修改到数据库并提交事务
- 发布领域事件通知并处理
我如何知道哪个领域事件被发布了呢?
首先需要根据领域事件的类型定义通知:
public interface IDomainEventNotification<out TEventType> where TEventType:IDomainEvent
{
TEventType DomainEvent { get; }
}
public class DomainNotificationBase<T> : IDomainEventNotification<T> where T:IDomainEvent
{
public T DomainEvent { get; }
public DomainNotificationBase(T domainEvent)
{
this.DomainEvent = domainEvent;
}
}
public class OrderPlacedNotification : DomainNotificationBase<OrderPlaced>
{
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<IDomainEvent>>();
foreach (var domainEvent in domainEvents)
{
Type domainEvenNotificationType = typeof(IDomainEventNotification<>);
var domainNotificationWithGenericType = domainEvenNotificationType.MakeGenericType(domainEvent.GetType());
var domainNotification = _scope.ResolveOptional(domainNotificationWithGenericType, new List<Parameter>
{
new NamedParameter("domainEvent", domainEvent)
});
if (domainNotification != null)
{
domainEventNotifications.Add(domainNotification as IDomainEventNotification<IDomainEvent>);
}
}
var tasks = domainEventNotifications
.Select(async (notification) =>
{
await _mediator.Publish(notification, cancellationToken);
});
await Task.WhenAll(tasks);
下图显示了整个流程是如何执行的:

你可能会觉得有许多内容需要记忆,确实如此!但是实际上整个流程还是十分简单直接的,并且我们可以使用IOC拦截器来简化这个流程,
在后面的文章会说到。
总结
- 领域事件是集成根中发生的过去的某件事,这是领域驱动模式中非常重要的一个概念。
- 有多种发布和处理领域事件的方法-静态类、返回事件对象、通过集合暴露
- 领域事件应该在事务内处理
- 对于非事务内的操作,通过领域事件通知完成
浙公网安备 33010602011771号