领域驱动设计 (DDD):领域事件处理程序——将它们放在哪里?

Domain Driven Design (DDD): Domain Event Handlers – Where to place them?

我对在基于六边形架构的应用程序中的何处处理域事件感到困惑。我说的是限界上下文内部域事件,而不是上下文间 integration/application/public 事件。

背景

据我所知,应用程序逻辑(即用例逻辑、工作流逻辑、与基础设施的交互等)是命令处理程序所属的地方,因为它们特定于某个应用程序设计 and/or UI设计。命令处理程序然后调用域层,所有域逻辑都驻留在该层(域服务、聚合、域事件)。领域层应该独立于特定的应用程序工作流and/orUI设计。

在许多资源(博客、书籍)中,我发现人们在应用层实现域事件处理程序,类似于命令处理程序。这是因为域事件的处理应该在它自己的事务中完成。由于它可能会影响其他聚合,因此必须首先通过基础设施加载这些聚合。然而,关键点在于:领域事件被分解并转化为一系列对聚合的方法调用。这个重要的翻译只存在于应用层。

问题

我认为关于 哪些领域事件对其他聚合有什么影响 的知识是领域知识本身不可或缺的一部分。如果我要删除除我的域层之外的所有内容,这些知识不应该保留在某个地方吗?在我看来,我们应该将领域事件处理程序直接放在领域层本身:

当然,为了加载受影响的聚合,我们仍然需要在应用层中有一个相应的处理程序。此处理程序仅启动一个新事务,加载感兴趣的聚合并调用域层。

因为我从来没有在任何地方看到过这个,我想知道我是否对 DDD、领域事件或应用层和领域层之间的区别有什么误解。

编辑:示例

让我们从这个常用的方法开始:

// in application layer service (called by adapter)
public void HandleDomainEvent(OrderCreatedDomainEvent event) {
    var restaurant = this.restaurantRepository.getByOrderKind(event.kind);
    restaurant.prepareMeal(); // Translate the event into a (very different) command - I consider this important business knowledge that now is only in the application layer.
    this.mailService.notifyStakeholders();
}

改用这个怎么样?

// in application layer service (called by adapter)
public void HandleDomainEvent(OrderCreatedDomainEvent event) {
    var restaurant = this.restaurantRepository.getByOrderKind(event.kind);
    this.restaurantDomainService.HandleDomainEvent(event, restaurant);
    this.mailService.notifyStakeholders();
}

// in domain layer handler (called by above)
public void HandleDomainEvent(OrderCreatedDomainEvent event, Restaurant restaurant) {
    restaurant.prepareMeal(); // Now this translation knowledge (call it policy) is preserved in only the domain layer.
}

您的描述听起来很像事件溯源。

如果是事件溯源(聚合的状态完全来自领域事件),那么事件处理程序就在领域层,事实上一般趋势是 port/adapter/anti-corruption-layer发出命令;然后,聚合的命令处理程序(如有必要)使用事件处理程序来导出聚合的状态,然后基于状态和命令发出持久化的事件,以便事件处理程序可以导出下一个状态。请注意,这里的事件处理程序肯定属于领域层,命令处理程序也可能属于领域层。

在我看来,更一般的事件驱动方法倾向于隐含地利用这样一个事实,即一方的事件通常是另一方的命令。

值得注意的是,事件在某种意义上通常只是聚合上的具体化方法调用。

大多数偶数处理程序 classes 的问题是它们通常与特定的消息传递技术相关联,因此通常位于 infrastructure layer.

但是,没有什么能阻止您编写与技术无关的处理程序并使用向它们发送的技术感知适配器。

例如,在我构建的一个应用程序中,我有一个需要操作的策略 的概念。只要策略规则为 satisfied/unsatisfied,该策略就会将给定 工作项 的 assignment/un-assignment 驱动到一个特殊的工作负载桶。在许多情况下必须重新评估策略,例如当文档附加到 Work Item 时,当分配 Work Item 时,当已授予外部状态标志等

我最终在具有 void when(CaseAssigned event) 等事件处理方法的域中创建了一个 ActionRequiredPolicy class,并且我在基础设施层中有一个偶数处理程序,它只是通知策略.

我认为人们将它们放在 infrastructureapplication 层中的另一个原因是,策略通常通过触发新命令来对事件作出反应。有时这种方法感觉很自然,但有时您想要明确表示动作 必须 发生以响应事件,否则不会发生:将事件转换为命令使它成为不太明确。

这是一个 older question 我问过的相关问题。

我遵循以下管理域事件的策略:

首先,最好将它们保存在事件存储中,这样您就可以在触发事件的事实(例如,创建用户)和它触发的操作(例如,向用户发送电子邮件)。

假设我们有一个命令总线:

  • 我在它周围放置了一个装饰器,用于保留命令生成的事件。
  • 工作人员处理事件存储并在有界上下文 (BC) 之外发布事件。
  • 其他对活动感兴趣的BC(或发布它的BC),请订阅它。事件处理程序就像命令处理程序一样,属于应用层。

如果您使用六边形架构,六边形将分为应用层和域。