DDD - DTO应该在哪个层中实现?

85

我正在学习DDD,如果我的问题很幼稚,请见谅。我认为我需要使用本地数据传输对象来向用户显示数据,因为许多属性不属于任何实体/值对象。

然而,我不确定这个DTO应该在哪里实现 - 在域层还是在应用程序服务层。DTO的实现似乎是域的一部分,但这意味着当我在服务层中创建DTO集合并将其传递给演示层时,我必须在演示层中引用域层,这似乎是错误的。

使用DDD原则实现DTO的正确方法是什么?


5
你为什么认为DTO是领域的一部分?DTO是可序列化友好的技术表示。 - plalx
据我所知,它是数据层的一部分。领域层包括用例、存储库(不含实现)和模型(领域模型)。另一方面,数据层包括存储库(实现)、数据源,其中包括DTO和实体。 - Bitwise DEVS
5个回答

64

将DTO定义到源值来自的层级。

相对于OP的问题:将DTO放在应用服务层中。 DTO是该层的输出,如果您在此定义它,则很合理。不要将DTO放在领域层中。领域层不关心将事物映射为服务外部层(领域不知道其外部世界存在)。

表示层(面向消费者)

  • 这可以是您的API
  • 具有相对于其层级的属性的模型或DTO定义。如果这是一个API,则Models / DTO具有用于格式化或数据类型验证的属性
  • 这是“应用程序根”(意味着它必须引用Domain Service层,Data / Infrastructure层才能注入服务)
  • 在ApplicationService.Dto和Presentation.Dto之间映射数据

应用服务层

  • 具有自己的DTO定义,以便能够返回而无需公开领域实体。
  • Presentation层和Domain层之间的桥梁。
  • 包含应用程序服务。有关应用程序服务的详细定义,请参见答案https://dev59.com/62865IYBdhLWcg3wZNnT#3840552

领域层

  • 领域实体
  • 可能包含用业务可理解的词语定义的与基础架构层进行桥接的界面(IE:IExcelReport,IGoogleSheetReport,IRepository)。
  • 可能包含“领域服务”

Data / Infrastructure Layer(最接近您的数据库或外部服务)

  • 数据库架构(映射)。
  • 如果将此层定义为基础结构代码,则包括Excel库。
  • 邮件或通知服务。
  • PDF输出文件

1
不错的回答...我猜你所说的服务层是领域服务层 - 最好澄清一下,以免与应用程序服务层混淆。 - Hooman Bahreini
12
即使域服务属于域,也应该放在应用服务层。 - Code Name Jack
7
域服务与应用服务无关。 - Gabriel
10
我认为这个答案在应用服务和领域服务之间,以及应用层和领域层之间造成了混淆。 - Timo
11
在DDD中没有领域服务层。DDD有自然属于领域层的“领域服务”。在某些情况下,当领域服务需要与其他BC或外部系统交互时,领域层中会创建领域服务“接口”,而基础设施层中会创建领域服务“实现”。请参考Millett&Tune的书中第17-6个清单周围的讨论。 - Paulo Merson
显示剩余6条评论

32
这些暴露给外部的DTO成为合同的一部分。根据它们的形式,一个好的位置是应用层或表示层。
如果DTO仅用于演示目的,则表示层是一个不错的选择。
如果它们是API的一部分,无论是输入还是输出,这都是应用程序层的问题。应用层连接领域模型与外界。
有趣的是,表示层应该只通过应用层访问领域模型。否则,我们将失去单点访问——我们将有多个层调用领域模型。应用层公开了我们所有的用例。无论它们是由另一个服务的调用还是由表示层的调用引发的,都没什么区别。
来源:
我从Vaughn Vernon的The Red Book中学到了这些概念的核心。 (我想引用一下它,但是现在没有方便的方法。)关于应用层和表示层的章节是相关的。
主要地,我的结论来自于对Eric Evans和Vaughn Vernon所述概念的严格遵守,并优先考虑领域模型自由,因为这是领域驱动设计。
  • 领域模型应该易于更改。这意味着不要在外部暴露领域对象,因为存在外部依赖会使它们难以更改(而不破坏其他内容)。
  • 应用层是外部访问点。它定义了领域模型上的用例。这意味着不要从其他地方操作领域模型。演示层只能通过应用层进行。没有人喜欢处理多个不同的访问点!

这听起来是一个合理的答案。在我看来,应用层是有意义的,因为它是应用程序的边界,因此只有 DTO(数据传输对象)进出。请问您能否在回答中添加一些参考资料、研究、文章或支持性陈述? - kravemir
2
@kravemir 我同意你的观点。我想补充一下,演示层可以直接从应用层借用DTOs,因为它们是合同的一部分(因此具有抗变性)。或者,演示层可能有自己的理由更喜欢DTOs,例如当一个完全不同的形状更有意义时,可以构建视图。当然,代价就是额外的翻译工作。因此,我建议首先在应用层的DTO上进行开发,并根据需要进行调整。 - Timo

17
Yorro正确指出了DTO放置的位置,但我鼓励您避免"DTO思维方式"。这种思考方式与DDD的思考方式相冲突。
思考"I need a DTO here"是在考虑技术表示(如plalx所说);这是一个太低的抽象级别。尝试更高层次的抽象,考虑您的领域、用户任务和UI。
您需要将视图数据传递给用户吗?通过返回一个特定的YourViewInfo类的View Service将其带到UI中。
您需要将数据发送到某个服务以执行任务吗?将其发送到一个特定的TaskMessageInfo类或特定的Command类。
当您开始建模这些类的内部时,才应开始考虑其技术表示;然后您可以得出结论,可能是方便使用DTO类。
这样思考有助于您对系统进行建模,并且不会引发以下问题:
“这个东西放在哪里或者属于哪里?”

2
你的意思只是使用不同的名称吗?YourViewDTO 而不是 YourViewInfo? - Markus Pscheidt
2
@MarkusPscheidt 不是的。我的观点是,在一个领域上下文化的架构(DDD)中,DTO并不意味着什么,因此您不能问它们放在哪里。如果您正在概括概念以解释某些内容,则可以谈论DTO,但在您的架构中,您不应该对DTO进行建模;您应该对上下文进行建模,而上下文决定了模型的放置位置。我的观点是,在DDD世界中,这个问题没有意义。 - jlvaquero

14

1

六边形架构(端口/适配器)

这里要提到的是所谓的六边形(端口/适配器)架构[Vernon, 红书第125页]。它非常方便地放置了代表外部(领域和应用程序之外)消费者的数据对象。该架构是DDD通常隐含的分层架构的重要补充。

以下是示例。

除了DB、电子邮件服务等端口/适配器之外,我们还可以定义一个ports/adapters/http/ui/myestore/ShoppingCartResponse.valueobject.ext(假设我们使用一些虚构的编程语言EXT),其中包含您的UI应用程序MyEStore将用于显示最终用户的购物车状态的数据。

ShoppingCartResponse.valueobject.extports/adapters/http/ui/myestore/EStoreHTTP.adapter.ext创建(在我们的示例中,为简洁起见,它可以是来自REST世界中非常轻巧的HTTP REST API控制器的同义词)。

适配器请求领域服务ShoppingCart.service.ext获取聚合、实体、其他值对象。然后它从它们中创建所需的ShoppingCartResponse.valueobject.ext(由自己或使用创建者-工厂、构建器等)。然后将值对象作为HTTP响应发送给消费者。

DTO还是值对象?

您应该根据以下内容决定ShoppingCartResponse是否为值对象或DTO(.dto.ext):

  • Ports/Adapters对象层次结构的特定结构;
  • 系统是否有另一种类型的对象,即DTO,在系统中合理,还是只保留值对象更好;
  • DTO与值对象在对象层次结构中的语义含义;
  • 它们之间的职责区分:例如,您的值对象可能会做一些不变量逻辑保持,而DTO可能只是一个没有逻辑的愚蠢对象。

我更喜欢从最简单的方法开始,只允许使用值对象,并且仅在出现明确的架构需求时添加DTO。

这种方法既具有很大的灵活性,又使代码设计简洁明了。端口/适配器部分容纳了彼此相关的对象(适配器、VO或DTO及其创建者),并保持应用程序层的清晰,为更相关的应用程序层对象保留空间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接