Domain driven design DDD存储库和REST

Domain driven design DDD存储库和REST,domain-driven-design,ddd-repositories,cqrs,Domain Driven Design,Ddd Repositories,Cqrs,DDD存储库是否应该始终返回聚合及其所有值对象和实体 例如,我有一个Invoice对象,它有它的类型和项目 Invoice --Id --Issuer --InvoiceType --Items 数据保存在4个SQL表中 Invoices (FK to invoice type, FK to issuers), InvoiceTypes Items(fk to Invoice) Issuers 如果存储库总是在其完整状态下返回聚合,那么如果我需要获取50张发票,并且只显示ID

DDD存储库是否应该始终返回聚合及其所有值对象和实体

例如,我有一个Invoice对象,它有它的类型和项目

Invoice
  --Id
  --Issuer
  --InvoiceType
  --Items
数据保存在4个SQL表中

Invoices (FK to invoice type, FK to issuers),
InvoiceTypes
Items(fk to Invoice)
Issuers
如果存储库总是在其完整状态下返回聚合,那么如果我需要获取50张发票,并且只显示ID和IssuerName,那么包含InvoiceType和Items是否有点过火

举例

InvoiceRepository
{
  //should this also fetch InvoiceTypes and items from SQL, or i need separate invoice model for this
  public List<Invoice> FetchForListing(int page, int take);
}
InvoiceRepository
{
//这是否也应该从SQL中获取发票类型和项目,或者我需要单独的发票模型
公共列表FetchForListing(int page,int take);
}
DDD存储库是否应该始终返回聚合及其所有值对象和实体

不需要。在您将要执行写入的用例中,您应该加载所有内容,因为您需要完整的内部状态来确保您的更改满足不变量

但是,如果您只打算执行读取,则完全不需要完整状态——将其限制在您提取的数据是合理的

(例如:在使用模式时,读取往往根本不会触及聚合,而是将聚合状态的“投影”中的数据复制到更合适的表示形式中。)

InvoiceRepository
{
//如果这也从SQL获取InvoiceType和items,
//或者我需要单独的发票型号
公共列表FetchForListing(int page,int take);
}
因此,在本例中,您不会返回
列表
,因为这不是您想要的,并且您可能不会使用相同的接口来表示存储库

InvoiceSummaryRepository
{
    public List<InvoiceSummary> readSummary(int page, int take);
}
InvoiceSummaryRepository
{
公共列表读取摘要(整版,整版);
}
检查您自己的通用语言,找出实际调用了什么
InvoiceSummary
,确定
List
是否实际上是一个有自己名称的东西(很可能您正在使用它在RESTAPI中构建资源的表示),等等

DDD存储库是否应该始终返回聚合及其所有值对象和实体

不需要。在您将要执行写入的用例中,您应该加载所有内容,因为您需要完整的内部状态来确保您的更改满足不变量

但是,如果您只打算执行读取,则完全不需要完整状态——将其限制在您提取的数据是合理的

(例如:在使用模式时,读取往往根本不会触及聚合,而是将聚合状态的“投影”中的数据复制到更合适的表示形式中。)

InvoiceRepository
{
//如果这也从SQL获取InvoiceType和items,
//或者我需要单独的发票型号
公共列表FetchForListing(int page,int take);
}
因此,在本例中,您不会返回
列表
,因为这不是您想要的,并且您可能不会使用相同的接口来表示存储库

InvoiceSummaryRepository
{
    public List<InvoiceSummary> readSummary(int page, int take);
}
InvoiceSummaryRepository
{
公共列表读取摘要(整版,整版);
}

检查您自己的通用语言,找出实际调用了什么
InvoiceSummary
,以确定
List
是否实际上是一个具有自己名称的东西(很可能您正在使用它在RESTAPI中构建资源的表示),等等。

哦,我明白了。我错误地理解了存储库与聚合的关系。我一直认为我必须返回Invoice域对象,但事实上它可以是业务所需的任何表示。在这种情况下,发票摘要是什么?域模型、实体、值对象?这里的InvoiceSummary只是数据/查询的读取模型-不涉及任何业务规则,因此我不必担心尝试将DDD名称应用于it技术,它可能是一个值对象-这是一个读取用例,因此数据应该是不可变的。我同意@tomliversidge的观点,这不值得担心。答案在技术上是正确的。但是,请考虑一下命名是否更加明确。理想情况下,
存储库
应该关注聚合。您可以将读取位命名为
InvoiceQuery
。它可以返回任何有用的数据表示,永远不应该关心聚合。您可以返回计数、DTO(如答案中所示)的整数,甚至是.Net世界中的
动态
数据行
。但是,是的,这两个概念在理想情况下应该分开。我同意@EbenRoux的观点,即避免
存储库
概念过载。当应用CQRS原则时,存储库应该只返回聚合根,而不是查询DTO。我错误地理解了存储库与聚合的关系。我一直认为我必须返回Invoice域对象,但事实上它可以是业务所需的任何表示。在这种情况下,发票摘要是什么?域模型、实体、值对象?这里的InvoiceSummary只是数据/查询的读取模型-不涉及任何业务规则,因此我不必担心尝试将DDD名称应用于it技术,它可能是一个值对象-这是一个读取用例,因此数据应该是不可变的。我同意@tomliversidge的观点,这不值得担心。答案在技术上是正确的。但是,请考虑一下命名是否更加明确。理想情况下,
存储库
应该关注聚合。您可以将读取位命名为
InvoiceQuery
。它可以返回任何有用的数据表示,永远不应该关心聚合。您可以返回计数、DTO(如答案中所示)的整数,甚至是.Net世界中的
动态
数据行
。但是,是的,这两个概念在理想情况下应该分开。我同意@EbenRoux的观点,即避免
存储库
概念过载。应用CQRS原则时,存储库应仅返回聚合