【DDD】领域事件

领域事件何时发布

领域事件的发布应该放在聚合中,因为无论是应用服务还是领域服务,最终要调用聚合根中的方法来操作聚合,我们这样做可以确保领域事件不会被漏掉。并且应该在SaveChange()时,发布事件,否则会造成重复发布或发布太早。
微软开源的eShopOnContainers项目中的做法:把领域事件的发布延迟到上下文保存修改时。实体中只是注册要发布的领域事件,然后在上下文 的SaveChanges方法被调用时,我们再发布事件。

实现

IDomainEvents:
供聚合根进行事件注册的接口

public interface IDomainEvents
{
    IEnumerable<INotification> GetDomainEvents();
    void AddDomainEvent(INotification eventItem);
    void AddDomainEventIfAbsent(INotification eventItem);
    void ClearDomainEvents();
}

BaseEntity:

public abstract class BaseEntity : IDomainEvents
{
    private List<INotification> DomainEvents = new();

    public void AddDomainEvent(INotification eventItem)
    {
        DomainEvents.Add(eventItem);
    }

    public void AddDomainEventIfAbsent(INotification eventItem)
    {
        if (!DomainEvents.Contains(eventItem))
        {
            DomainEvents.Add(eventItem);
        }
    }

    public void ClearDomainEvents()
    {
        DomainEvents.Clear();
    }

    public IEnumerable<INotification> GetDomainEvents()
    {
        return DomainEvents;
    }
}

Events:

public record UserAddedEvent(User Item):INotification;
public record UserUpdatedEvent(Guid Id):INotification;
public record UserSoftDeletedEvent(Guid Id):INotification;

实体:

public class User: BaseEntity
{
  public Guid Id { get; init; }
  public string UserName { get; init; }
  public string Email { get; private set; }
  public string? NickName { get; private set; }
  public int? Age { get; private set; }
  public bool IsDeleted { get; private set; }
  private User()
  {
      //提供无参构造方法。避免EF Core加载数据的时候调用有参的构造方法触发领域事件
  }
  public User(string userName,string email)
  {
      this.Id = Guid.NewGuid();
      this.UserName = userName;
      this.Email = email;
      this.IsDeleted = false;
      AddDomainEvent(new UserAddedEvent(this));
  }
  public void ChangeEmail(string value)
  {
      this.Email = value;
      AddDomainEventIfAbsent(new UserUpdatedEvent(Id));
  }
  public void ChangeNickName(string? value)
  {
      this.NickName = value;
      AddDomainEventIfAbsent(new UserUpdatedEvent(Id));
  }
  public void ChangeAge(int value)
  {
      this.Age = value;
      AddDomainEventIfAbsent(new UserUpdatedEvent(Id));
  }
  public void SoftDelete()
  {
      this.IsDeleted = true;
      AddDomainEvent(new UserSoftDeletedEvent(Id));
  }
}

BaseDbContext:

public abstract class BaseDbContext : DbContext
{
    private IMediator mediator;

    public BaseDbContext(DbContextOptions options, IMediator mediator) : base(options)
    {
        this.mediator = mediator;
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        throw new NotImplementedException("Don not call SaveChanges, please call SaveChangesAsync instead.");
    }

    public async override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        var domainEntities = this.ChangeTracker.Entries<IDomainEvents>()
                        .Where(x => x.Entity.GetDomainEvents().Any());
        var domainEvents = domainEntities
            .SelectMany(x => x.Entity.GetDomainEvents()).ToList();
        domainEntities.ToList()
            .ForEach(entity => entity.Entity.ClearDomainEvents());
        foreach (var domainEvent in domainEvents)
        {
            await mediator.Publish(domainEvent);
        }
        return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }
}
posted @ 2022-05-05 22:33  .Neterr  阅读(216)  评论(0编辑  收藏  举报