【问题标题】:How to handle domain events that are raised by event handlers?如何处理由事件处理程序引发的域事件?
【发布时间】:2014-10-31 15:04:06
【问题描述】:

我已经通过 jbogard 实现了以下模式:

http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/

想象以下实体Coupon 和事件CouponActivatedEvent

public class Coupon : DomainEntity
{
    public virtual User User { get; private set; }

    // ...omitted...

    public void Activate(User user)
    {
        if (User != null)
            throw new InvalidOperationException("Coupon already activated");

        User = user;

        Events.Add(new CouponActivatedEvent(this));
    }
}

以下事件处理程序CouponActivatedHandler

public class CouponActivatedHandler : IDomainEventHandler<CouponActivatedEvent>
{
    public void Handle(CouponActivatedEvent e)
    {
        // user gets 5 credits because coupon was activated
        for (int i = 0; i < 5; i++)
        {
            e.Coupon.User.AddCredit(); // raises UserReceivedCreditEvent and CreditCreatedEvent
        }
    }
}

以下 SaveChanges 覆盖 DbContext(实体框架 6),取自 jbogard 的博文:

public override int SaveChanges()
{
    var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
        .Select(po => po.Entity)
        .Where(po => po.Events.Any())
        .ToArray();

    foreach (var entity in domainEventEntities)
    {
        var events = entity.Events.ToArray();
        entity.Events.Clear();
        foreach (var domainEvent in events)
        {
            _dispatcher.Dispatch(domainEvent);
        }
    }

    return base.SaveChanges();
}

如果我们现在激活优惠券,这将提高CouponActivatedEvent。当调用SaveChanges 时,会执行处理程序,并会引发UserReceivedCreditEventCreditCreatedEvent。但他们不会被处理。我误解了模式吗?还是 SaveChanges 覆盖不合适?

我考虑过创建一个循环,在转到base.SaveChanges(); 之前一直重复直到没有引发新事件...但我担心我会意外创建无限循环。像这样:

public override int SaveChanges()
{
    do 
    {
        var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
            .Select(po => po.Entity)
            .Where(po => po.Events.Any())
            .ToArray();

        foreach (var entity in domainEventEntities)
        {
            var events = entity.Events.ToArray();
            entity.Events.Clear();
            foreach (var domainEvent in events)
            {
                _dispatcher.Dispatch(domainEvent);
            }
        }
    }
    while (ChangeTracker.Entries<IDomainEntity>().Any(po => po.Entity.Events.Any()));

    return base.SaveChanges();
}

【问题讨论】:

    标签: c# entity-framework domain-driven-design


    【解决方案1】:

    是的,你误会了。域事件与 C# 事件不同,它是关于域中发生了什么变化的消息。一条规则是,事件是已经发生的事情,它是过去的事情。因此,事件处理程序根本不能(它不应该)改变事件,就像改变过去一样。

    CouponActivatedHandler 至少应该从存储库中获取用户实体,然后使用积分数更新它,然后保存它,然后发布 UserCreditsAdded 事件。更好的是,处理程序应该只创建并发送一个命令 AddCreditsToUser

    使用域事件模式,操作只是一个命令链->事件->命令->事件等。事件处理程序通常是一个服务(或一个方法),它只处理那个位。事件的发送者对处理程序一无所知,反之亦然。

    作为一个经验法则,域对象在其状态发生变化时会生成一个事件。服务将接收这些事件,然后将它们发送到服务总线(对于简单的应用程序,DI 容器就足够了),服务总线将发布它们以供任何感兴趣的人处理(这意味着来自本地应用程序或订阅该总线的其他应用程序的服务)。

    永远不要忘记域事件是在构建应用程序架构时使用的高级模式,它不仅仅是执行对象事件(如 C# 的事件)的另一种方式。

    【讨论】:

    • 谢谢,你为我澄清了很多事情。您是否知道按照您描述的方式设计的示例项目?我想我明白了,我只是不确定如何正确实施。
    • 例如,并非如此。但是开始做吧,熟能生巧。做,注意什么感觉很麻烦,尝试重构等等。DDD 是通过这样做来学习的。
    • 我现在明白了,特别是事件处理程序对过去发生的事情做出反应解释了很多。
    • 你写了然后发布了一个 UserCreditsAdded 事件,它让我们回到原来的问题:我们应该如何处理事件处理程序生成的事件。
    • @PopovSergei 您可以根据需要进行处理。使用 ES,CQRS 意味着您有一个要更新的投影。或者该事件触发业务流程的下一步。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-24
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 2019-02-27
    • 2013-05-30
    • 2023-04-04
    相关资源
    最近更新 更多