DDD——应该实现哪一层DTO



我正在学习DDD,所以如果我的问题是天真的道歉。我认为我需要使用本地数据传输对象,以便向用户显示数据,因为许多属性不是任何实体/值对象的一部分。

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

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

为值的来源层定义DTO

相对于OP的问题:将DTO放在应用服务层中。DTO是那个层的输出,如果你在那里定义它是有意义的。不要将DTO放在域层中。域层不关心映射事物以服务于外部层(域不知道在它自己的世界之外还有一个世界)。

表示层 (close to consumer)

    这可能是你的API
  • 具有自己的模型或Dto定义,其属性相对于其层。如果这是一个API,那么模型/DTO有格式化或数据类型验证的属性
  • 这是"应用程序根目录"(这意味着它必须同时引用域服务层和数据/基础设施层才能注入服务)
  • 在ApplicationService之间映射数据。Dto和Presentation。Dto

  • 具有自己的Dto定义,以便能够返回数据而不暴露域实体。
  • 表示层和域层之间的桥梁。
  • 包含应用程序服务。有关应用程序服务的详细定义,请参阅https://stackoverflow.com/a/3840552/1027250。

领域层

  • 领域实体
  • 可能包含桥接基础设施层的接口,用业务可以理解的单词定义,不受技术术语(IE: IExcelReport, igooglesheereport, IRepository)的限制。
  • 可能包含"域服务"

数据/基础设施层(最接近数据库或外部服务)

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

这种对外公开的dto成为合同的一部分。根据它们的形式,应用程序层或表示层是放置它们的好地方。

如果dto仅用于表示目的,那么表示层是一个不错的选择。

如果它们是API的一部分,无论是用于输入还是输出,那都是应用层关注的问题。应用层是将你的领域模型与外部世界连接起来的。

作为一个有趣的观察,表示层应该只通过应用层访问域模型。否则,我们将失去单点访问——我们将有多个层调用域模型。应用层公开了我们所有的用例。无论它们是由来自另一个服务的调用调用还是由表示层调用,都没有什么区别。

来源

这些概念的核心是我从Vaughn Vernon的《红皮书》中学到的。(我想引用一下,但手边没有。)关于应用层和表示层的章节是相关的。

首先,我的结论来自于严格遵守Eric Evans和Vaughn Vernon提出的概念,并在领域模型中优先考虑自由,因为这是领域驱动的设计:

  • 域模型应该易于更改。这意味着不要对外公开域对象,因为拥有外部依赖项会使它们难以更改(而不会破坏东西)。
  • 应用层是外部访问点。它定义了域模型上的用例。这意味着不是从其他地方对域模型进行操作。表示层只能通过应用层。没有人喜欢处理许多不同的访问点!

Yorro关于DTO的位置是正确的,但我鼓励你避免"DTO思维"。这种思维方式与DDD的思维方式相冲突。

思考"我需要一个DTO在这里"是在思考技术表示(如plalx所说);这个抽象层次太低了。尝试更高层次的抽象,思考你的领域、用户的任务和你的UI。

你需要获取视图数据给用户吗?通过返回一个特定的YourViewInfo类的View Service把它带到UI。

是否需要发送数据到某个服务来执行任务?给它发送一个特定的TaskMessageInfo类或一个特定的Command类。

当你开始对这些类的内部建模时,你应该开始考虑它的技术表示;那么你就可以得出这样的结论:为了方便,可以使用DTO类。

以这种方式思考可以帮助您对系统进行建模,并且不会引发诸如

之类的问题。

这个东西该放在哪里?

DTO和Domain是不同的层。
所以它需要从一个映射到另一个,通常是在所谓的应用程序服务层完成的。请阅读以下文章,以深入了解DTO和分层:

  • 是分层值得映射?by Mark Seemann
  • DTO vs值对象vs POCO:定义

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

这里必须提到的是所谓的六边形(端口/适配器)架构[Vernon,红皮书第125页]。将表示外部(域外)数据的对象放置在域外非常方便。(应用程序)消费者。该体系结构是DDD通常隐含的分层体系结构的重要补充。

示例如下

与端口/适配器一起用于db,电子邮件服务等

我们可以定义一个ports/adapters/http/ui/myestore/ShoppingCartResponse.valueobject.ext(假设我们使用某种假想的编程语言EXT),其中包含UI应用程序MyEStore将用于向最终用户显示购物车状态的数据。

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

适配器向域服务ShoppingCart.service.ext请求聚合、实体和其他值对象。然后它从它们中创建所需的ShoppingCartResponse.valueobject.ext(通过自己或与创造者-工厂,建筑商等)。然后将值对象作为HTTP响应发送给消费者。

DTO还是值对象?

ShoppingCartResponse是值对象还是DTO (.dto.ext),您应该基于

来决定
  • 端口/适配器对象层次结构的特定结构;
  • 如果在系统中有另一种类型的对象(DTO)是合理的,或者最好只保留值对象;
  • 对象层次结构中DTO与值对象的语义含义;
  • 它们之间的责任区别:假设你的值对象会做一些不变的逻辑保持,而DTO可能只是一个没有逻辑的哑对象。

我更喜欢从最简单的方法开始,只允许值对象,并且只在它们中出现明确的体系结构需求时添加dto。

这种方法提供了很大的灵活性,但它保持代码设计干净和简单。端口/适配器部分容纳属于一起的对象(适配器,VO或DTO,它们的创建者),并保持应用层清洁,为更相关的应用层对象保留空间。

最新更新