【问题标题】:Are C# events feasible for domain events in DDD? [closed]C# 事件对于 DDD 中的域事件是否可行? [关闭]
【发布时间】:2019-11-10 17:32:18
【问题描述】:

我读到人们在他们的领域驱动设计中使用事件调度库来处理领域事件。

C# 语言使用 event 关键字和 EventHandler<> 类内置了对 events 的支持。用它代替事件调度库(如MediatR)是否可行?

我知道域事件通常在持久化时调度,而不是在调用聚合上的方法时调度。但是通过将事件添加到List<Action>,您可以推迟事件的引发。

事件声明:

public event EventHandler<InvoiceCreatedEventArgs> InvoiceCreated;

延迟事件引发:

private ICollection<Action> _events = new List<Action>();

public void AddDomainEvent(Action action)
{
    _events.Add(action);
}

protected virtual void OnInvoiceCreated(InvoiceCreatedEventArgs e)
{
    AddDomainEvent(() => { InvoiceCreated?.Invoke(this, e); });
}

如果事件作为根聚合的公共成员公开,那么每当从存储库中获取根聚合时,应用程序都必须重新订阅根聚合的每个新实例。

对于每个实例都必须重新订阅,这不是有点不受欢迎吗?应用程序是否也必须取消订阅?

除非事件被声明为static,但我听说过关于静态事件和内存泄漏的坏消息。这会是一个问题吗?

如果使用 C# 事件,它们是属于根聚合还是属于存储库?

如果事件是在存储库中声明的(可以是 Entity Framework Core DbContext),那么当使用 .AddDbContext 方法向 ASP.NET Core 依赖处理程序注册时,它将使用“Scoped " 生命周期(每个客户端请求一次),因此除非将事件声明为静态,否则应用程序必须重新订阅每个新传入的 HTTP 请求都会发生的存储库的每个新实例。

在采用域驱动设计的应用程序中将 C# 事件用于域事件是否可行,或者它只是一个不可行的命运多舛的想法?

【问题讨论】:

    标签: c# events domain-driven-design eventhandler


    【解决方案1】:

    如果不区分 C# 事件和领域事件,就很难遵循您的叙述。但是,如果我正确理解您的建议,您正在设想一个 C# 组件,它侦听实体事件流,然后通过 C# 事件将这些传入的域事件发布到应用程序中的侦听器。如果这就是你的提议,那么你必须努力工作才能让它发挥作用,而且效果不会很好。

    如果我们看一下 Mediatr,它会订阅一个事件源并创建新的命令处理器来处理传入的域事件。关键是它创建了新的命令处理器,以便能够在它们上调用方法。另外,领域事件和命令处理器之间是一一对应的,在系统启动时除了Mediatr本身什么都不需要。

    使用 C# 事件,命令处理器由某些东西创建,然后注册自己以接收特定类型的 C# 事件。命令处理器启动链接,而不是事件源订阅者。为了处理各种事件,在启动时,您必须至少创建每种类型的命令处理器中的一个,并让它自己注册为 C# 消息的接收者,该消息将域事件作为有效负载携带。

    那么当您开始扩展时会发生什么? Mediatr 可以很好地扩展,因为它为每个领域事件创建了一个命令处理器。您的建议不会扩展,因为要处理 2 个相同的事件类型,您需要手动创建 2 个命令处理器,并且每个命令处理器都将接收传入的域事件,因为它们都订阅了相同的 C# 事件。

    可以围绕所有这些混乱进行编码,但 Jimmy Bogard 已经这样做了。与其重写所有启动 NuGet 的方法,不如拉下 Mediatr,然后用您节省的所有时间与您的孩子一起玩。

    【讨论】:

    • 不,不是实体流监听器。聚合具有引发 C# 事件的方法。每个领域事件都会有一个 C# event 成员,因此如果一个事件有 2 个订阅者,则该事件将引发一次,并且两个处理程序仅接收一次。
    • 所以这仅适用于应用程序内生成的非持久事件?假设我是一个想要了解任何密码更改的组件。每次实例化可以更改密码的类时都需要通知我,以便我可以向该类注册我的事件处理程序。这意味着发布者需要能够在运行时发现所有可能的侦听器,以便它可以告诉那些侦听器他们需要侦听。对吗?
    • 这取决于聚合中的方法是立即引发事件,还是聚合通过将事件添加到列表中以供以后引发而推迟引发。 “发布者”是域方法,它只是添加一个事件,该事件在聚合持续存在时引发,然后通知所有事件订阅者。
    【解决方案2】:

    这取决于应用程序的类型。

    如果应用程序是 Web 应用程序,那么它的 DbContext 范围为 HTTP 请求的生命周期,并且所有聚合的生命周期也很短,持续 HTTP 请求的持续时间。因此,注册 C# 事件处理程序很麻烦,而且因为您必须在获取 DbContext 或聚合后执行它,它必须在控制器内部,重复,无处不在。因此,对于使用 C# 处理事件的 Web 应用程序来说,这是一个糟糕的选择,最好将其委托给一个单例类,就像使用 MediatR 所做的那样。

    如果应用程序维护一个在应用程序生命周期内存在的持久DbContext 和一个在应用程序生命周期内存在的根聚合,那么您可以使用 C# 事件并只需注册一次事件处理程序。这样的应用程序可以是命令行应用程序、后台服务或带有 UI 的桌面应用程序。

    【讨论】:

      猜你喜欢
      • 2019-12-07
      • 1970-01-01
      • 1970-01-01
      • 2019-08-23
      • 1970-01-01
      • 2016-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多