1 前置閱讀
在閱讀本文章之前,你可以先閱讀:
- DDD領域驅動設計是什麼
- DDD領域驅動設計:實體、值物件、聚合根
- DDD領域驅動設計:倉儲
- MediatR一個優秀的.NET中介者框架
2 什麼是領域事件?
領域事件是在領域中發生的事,你希望同一個領域(程式)的其他部分了解它。 通知部分通常以某種方式對事件作出反應。
3 實現領域事件?
重點強調領域事件釋出/訂閱是使用 MediatR 同步實現的。
首先,定義待辦事項已更新的領域事件
public class TodoUpdatedDomainEvent : INotification
{
public Todo Todo { get; }
public TodoUpdatedDomainEvent(Todo todo)
{
Todo = todo;
}
}
然後,引發領域事件,將域事件新增到集合,然後在提交事務之前或之後立即排程這些域事件,而不是立即排程到域事件處理程式 。
public abstract class Entity
{
//...
private List<INotification> domainEvents;
public IReadOnlyCollection<INotification> DomainEvents => domainEvents?.AsReadOnly();
public void AddDomainEvent(INotification eventItem)
{
domainEvents = domainEvents ?? new List<INotification>();
domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
domainEvents?.Remove(eventItem);
}
public void ClearDomainEvents()
{
domainEvents?.Clear();
}
//... 其他程式碼
}
要引發事件時,只需將其在聚合根實體的方法處新增到程式碼中的事件集合。
public class Todo : AggregateRoot
{
//...
public void Update(
string name)
{
Name = name;
AddDomainEvent(new TodoUpdatedDomainEvent(this));
}
//... 其他程式碼
}
請注意 AddDomainEvent 方法的唯一功能是將事件新增到列表。 尚未排程任何事件,尚未呼叫任何事件處理程式。你需要在稍後將事務提交到資料庫時排程事件。
public class Repository : IDisposable, IRepository
{
//...
private readonly IMediator mediator;
private readonly DbContext context;
public Repository(DbContext context, IMediator mediator)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
this.mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
public void Save()
{
mediator.DispatchDomainEvents(context);
context.SaveChanges();
}
public static void DispatchDomainEvents(this IMediator mediator, DbContext ctx)
{
var domainEntities = ctx.ChangeTracker
.Entries<Entity>()
.Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
var domainEvents = domainEntities
.SelectMany(x => x.Entity.DomainEvents)
.ToList();
domainEntities.ToList()
.ForEach(entity => entity.Entity.ClearDomainEvents());
foreach (var domainEvent in domainEvents)
mediator.Publish(domainEvent);
}
//... 其他程式碼
}
最後,訂閱並處理領域事件
public class TodoUpdatedDomainEventHandler : INotificationHandler<TodoUpdatedDomainEvent>
{
private readonly ILoggerFactory logger;
public TodoUpdatedDomainEventHandler(ILoggerFactory logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public Task Handle(TodoUpdatedDomainEvent todoUpdatedDomainEvent, CancellationToken cancellationToken)
{
logger.CreateLogger<TodoUpdatedDomainEvent>().LogDebug("Todo with Id: {TodoId} has been successfully updated",
todoUpdatedDomainEvent.Todo.Id);
return Task.CompletedTask;
}
}