12

我正在使用 CQRS 和域事件概念构建具有域模型的应用程序(但没有事件源,只是普通的旧 SQL)。SomethingChanged 类型的事件没有问题。然后我陷入了实现SomethingCreated事件的困境。

当我创建一个映射到具有标识主键的表的实体时,在实体被持久化之前我不知道 Id。实体是持久性无知的,因此当从实体内部发布事件时,Id 是未知的 - 它仅在调用 context.SaveChanges() 后神奇地设置。那么如何/在哪里/何时可以将 Id 放入事件数据中?

我在想:

  • 包括对事件中实体的引用。这将在域内工作,但不一定在具有多个自治系统通过事件/消息进行通信的分布式环境中工作。
  • 覆盖 SaveChanges() 以某种方式更新排队等待发布的事件。但是事件是不可变的,所以这看起来很脏。
  • 摆脱身份字段并使用实体构造函数中生成的 GUID。这可能是最简单的,但可能会影响性能并使其他事情变得更加困难,例如调试或查询(where id = 'B85E62C3-DC56-40C0-852A-49F759AC68FB'、 noMINMAX)。这就是我在许多示例应用程序中看到的。
  • 混合方法 - 不理会身份,主要将其用于外键和更快的连接,但使用 GUID 作为唯一标识符,我通过它从应用程序的存储库中提取实体。
4

1 回答 1

10

我个人喜欢唯一标识符的 GUID,尤其是在数字 id 会导致问题的多用户分布式环境中。因此,我从不使用数据库生成的标识列/属性,这个问题就消失了。

除此之外,由于您遵循 CQRS,因此您无疑拥有一个 CreateSomethingCommand 和相应的 CreateSomethingCommandHandler,它们实际上执行了创建新实例并使用存储库(通过 context.SaveChanges)持久化新对象所需的步骤。我将在此处而不是在域对象本身中引发 SomethingCreated 事件。

一方面,这解决了您的问题,因为命令处理程序可以等待数据库操作完成,提取身份值,更新对象然后在事件中传递身份。但是,更重要的是,它还解决了对象究竟是何时“创建”的棘手问题?

在构造函数中引发域事件是不好的做法,因为构造函数应该是精简的并且只需执行初始化。另外,在您的模型中,对象只有在分配了 ID 后才会真正创建。这意味着在构造函数执行后需要额外的初始化步骤。如果您有多个步骤,您是执行执行顺序(另一种反模式)还是检查每个步骤以识别它们何时都完成(哦,臭)?希望你能看到这如何迅速失控。

因此,我的建议是从命令处理程序中引发事件。(注意:即使您切换到 GUID 标识符,我也会遵循这种方法,因为您永远不应该从构造函数中引发事件。)

于 2012-07-02T16:34:54.687 回答