CQRS:Class 冗余并将 DTO 传递给域

CQRS: Class Redundancy and passing DTO to Domain

我的 CQRS 应用程序有一些复杂的域对象。创建时,实体的所有属性都是由用户直接指定的,所以

CreateFooCommand 有大约 15 个属性。

FooCreatedEvent 因此也有 15 个属性,因为我需要读取端的所有实体属性。

由于必须将命令参数分派到域对象并且不应将 FooCreatedCommand 传递到域,

有一个从 CreateFooCommand 到域的手动映射。

由于域应该创建域事件,

这是从域 Foo 属性到 FooCreatedEvent 的另一个映射。

在读取方面,我使用 DTO 来表示 Foo 的结构,因为它存储在我的读取模型中。

因此更新读取端的事件处理程序引入了另一个从事件参数到 DTO 的映射。

要实施一个简单的业务案例,我们有

我想摆脱 command/event 参数并推动 DTO 对象,但这意味着域可以接收或创建 DTO 并将其分配给事件。

序列:

REST Controller --Command+DTO--> Command Handler --DTO--> Domain --(Event+DTO)--> Event Handler

关于减少 CQRS 实施痛苦的任何想法?

我看到以下选项:

  1. 创建一个不可变的 DTO class FooDetails,通过将其注入到构造函数中,供 CreateFooCommandFooCreatedEvent 使用;类型提示针对 FooDetails 的聚合方法;例如 new CreateFooCommand(new FooDetails(prop1, prop2, ...))

  2. 创建一个由 CreateFooCommandFooCreatedEvent 继承的不可变基础 class FooDetails 并键入针对 [= 的聚合方法提示10=]

  3. 完全改变风格,使用cqrs.nu提倡的风格,命令直接发送到聚合;聚合体有像 FooAggregate::handle(CreateFooCommand command) 这样的命令方法;我个人经常使用这种风格。

使用 CQRS + ES,您选择了一种包含更多移动部件的更复杂的方法,因为您知道它可以让您实现更多目标。忍受它。这种方法的优势在于分离关注点。 Command 是命令,Event 是事件,等等。尽管它们中的许多在链条上可能看起来相似,但可能会有例外。有些可能包含额外的数据,或相同数据的略有不同的方面。命令可以包含与域无关的有关应用上下文的元信息(谁启动命令、何时、是否重试等)。阅读模型通常会包括有关要显示的相关对象的信息以及它们自己的信息(想想父子关系)。

在您阻止自己对这些异常进行建模之前,您只能删除这么多看似相似的代码。在这些数据结构之间引入继承或组合通常比必须编写样板映射代码的原始痛苦更复杂。