域驱动设计(DDD):域事件处理程序——将它们放置在何处



我对在基于六边形体系结构的应用程序中在哪里处理域事件感到困惑。我说的是有界上下文内部域事件,而不是上下文间集成/应用程序/公共事件。

背景

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

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

问题

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

  • 它们可以是域服务,接收域事件和可能受其影响的聚合,并将域事件转换为一个或多个方法调用。

  • 它们可以是聚合本身的方法,直接消耗整个域事件(即签名包含域事件类型),并对其执行任何操作。

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

由于我从未在任何地方看到过这一点,我想知道我是否对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.
}

大多数偶数处理程序类的问题是,它们通常与特定的消息传递技术绑定,因此通常被放置在基础结构层中。

但是,没有什么可以阻止您编写与技术无关的处理程序,并使用分派给它们的技术感知适配器。

例如,在我构建的一个应用程序中,我有一个ActionRequiredPolicy的概念。只要满足/不满足策略规则,该策略就会将给定的工作项分配/取消分配到特定的工作负载存储桶。在许多情况下,必须重新评估该策略,例如当文档附加到工作项时,当分配

任务项我最终在域中创建了一个ActionRequiredPolicy类,该类具有事件处理方法(如void when(CaseAssigned event)),并且在基础结构层中有一个偶数处理程序,它只是通知策略。

我认为人们将它们放在infrastructureapplication层的另一个原因是,策略通常通过触发新命令来对事件做出反应。有时这种方法感觉很自然,但在其他时候,您希望明确一个动作必须响应事件而发生,否则就不可能发生:将事件转换为命令会使其不那么明确。

这是我问的一个与此相关的老问题。

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

如果事件源(聚合的状态仅从域事件派生),那么事件处理程序在域层,事实上,通常的趋势是让端口/适配器/反腐败层发出命令;聚合的命令处理程序然后(如果需要)使用事件处理程序来派生聚合的状态,然后根据状态和命令发出持久化的事件,以便事件处理程序可以派生下一个状态。请注意,在这里,事件处理程序肯定属于域层,命令处理程序也可能属于域层。

在我看来,更普遍的事件驱动方法往往隐含地利用这样一个事实,即一方的事件往往是另一方的命令。

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

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

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

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

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

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

最新更新