在 DDD/CQRS 中,ReadModel 是否应该充当 ViewModel,如果不是,那么映射的责任在哪里?

In DDD/CQRS, should ReadModel act as ViewModel, if not then where belongs responsibility for mapping?

假设读取模型 ProductCatalogueItem 是从 aggregates/write-models 构建的,与写入模型分开存储,包含每个可供销售的产品,并具有以下属性:

而且,有两种看法:

自然地,想到了两个 ViewModel:

现在,..有两个选择(我可以看到)。


  1. ViewModels 是 ReadModels1:1 的表示

因此有两个读取模型,而不是一个,ProductCatalogueListItemProductCatalogueItemDetails。并且,读取服务将有两种方法:

并且,控制器 return 这些模型直接(或者,映射到传输层的 dto)。

这里的问题是过滤.. 读取服务是否应该在不同的读取模型上执行搜索查询,而不是从方法调用中 returned?因为,ProductCatalogueListItem 没有足够的信息来执行过滤。


  1. ViewModels 是 ReadModels 的另一个项目

读取服务将有两种方法:

而且,从ReadModels到ViewModels的映射是由上层(可能是controller)完成的。

过滤没有问题,...但是,还有另一个问题,即离开域层的数据比实际需要的多。而且,控制器会随着更多的逻辑而增长。由于不同的传输技术可能有不同的控制器,因此映射代码可能会在这些控制器中重复。


根据DDD/CQRS,哪种组织职责的方法是正确的,或者完全不同?

重点是:

首先,你做一个错误的断言:

...read model ProductCatalogueItem is built from aggregates/write-models...

读取模型不知道聚合或任何关于写入模型的信息,您直接从数据库构建读取模型,返回 UI.

所需的数据

所以,视图模型是读模型,它不涉及写模型。这就是 CQRS 存在的原因:为了拥有不同的模型,即读取模型,以优化查询以返回客户端所需的数据。

更新

我会尝试更好地解释自己:

CQRS 只是根据方法类型将一个对象一分为二。有两种方法类型:命令(任何改变状态的方法)和查询(任何 returns 值的方法)。就这些了。

当您将此模式应用于应用程序的服务边界时,您将拥有一个写入服务和一个读取服务,因此您可以以不同方式扩展命令和查询处理,并且您还可以拥有两个模型。

但是 CQRS 没有两个数据库,不是消息传递,不是最终一致性,不是从写入模型更新读取模型,不是事件源。您可以在没有它们的情况下执行 CQRS。我这么说是因为我在你的断言中看到了一些误解。

也就是说,读取模型的设计是根据用户希望在UI中看到什么信息来设计的,即读取模型是视图模型,它们之间没有映射关系,他们都是同一个模型。您可以在下面的参考文献 (3) 和 (6) 中阅读相关信息。我认为这是对你整个问题的回答。我不明白的是过滤问题。

一些很好的参考资料

(1) http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/

(2) http://www.cqrs.nu/Faq/command-query-responsibility-segregation

(3) “实施领域驱动设计”一书,作者 Vaughn Vernon。第 4 章:架构,“命令-查询责任分离,或 CQRS”部分

(4) https://kalele.io/really-simple-cqrs/

(5) https://martinfowler.com/bliki/CQRS.html

(6) http://udidahan.com/2009/12/09/clarified-cqrs/

由于您已经使用来自一个或多个服务的数据构建了读取模型,因此您的问题现在出在另一个 space(可能是 MVC)而不是 CQRS 中。

现在假设您的读取模型是一个数据库对象,ProductCatalogueListItem 和 ProductCatalogueItemDetails 是 2 个视图模型。当您请求提供产品列表时,您将从读取模型 (ProductCatalog table) 中在读取数据库中进行查询。可能是您使用其他 where 子句查询其他过滤器。现在,在获取数据库对象后,您将映射活动放在代码中的什么位置?这是个人选择。你根本不必在上层做这件事。当我使用 dapper 时,我使用通用内部的视图模型获取数据库对象。所以我可以直接从我的服务方法中得到 return 结果,其 return 类型将是 IEnumerable。

对于详细视图,我将使用相同的数据库对象。我知道 CQRS 建议对不同的视图使用不同的读取模型。但是问问自己——你真的需要另一个 db 对象来查看详细信息吗?您只需要一个 id 即可获取所有列,而在第一种情况下您需要一些选定的列。所以我会用上面提到的 2 种方法的混合来设计你的案例 - 有 2 种服务方法 returning 2 个不同的对象,而不是让 1:1 读取模型来查看模型有一个读取数据库对象并从中构建 2 个不同的视图模型。