【问题标题】:Microservices Restful API - DTOs or not?微服务 Restful API - DTO 与否?
【发布时间】:2017-06-15 16:06:38
【问题描述】:

REST API - DTOs or not?

我想在微服务的上下文中重新提出这个问题。这是原始问题的引述。

我目前正在为一个项目创建一个 REST-API 并且一直在阅读 关于最佳实践的文章。许多人似乎反对 DTO 只是公开域模型,而其他人似乎 认为 DTO(或用户模型或任何你想叫它的东西)不好 实践。个人觉得这篇文章很有道理。

不过,我也理解 DTO 的缺点 映射代码,域模型可能 100% 相同 DTO-counterpart 等等。

现在,我的问题

我更倾向于在我的应用程序的所有层中使用一个对象(换句话说,只公开域对象而不是创建 DTO 并手动复制每个字段)。我的 Rest 合同与域对象的差异可以使用 Jackson 注释来解决,例如 @JsonIgnore@JsonProperty(access = Access.WRITE_ONLY)@JsonView 等)。或者,如果有一个或两个字段需要使用 Jackson Annotation 无法完成的转​​换,那么我将编写自定义逻辑来处理这个问题(相信我,在我的 5 年多的时间里,我什至没有遇到过这种情况休息服务的长途旅行)

我想知道我是否错过了没有将域复制到 DTO 的任何真正的不良影响

【问题讨论】:

  • 与链接的问题一样,这完全是基于意见的(尽管它确实是一个非常好的问题)
  • @FedericoPeraltaSchaffner 感谢 cmets。我正在寻找更多数据点/事实,而不是意见 :) 如果我无法从 Stackoverflow 平台得到这个问题的答案,我可能无法从其他任何地方得到它。恕我直言,Stackoverflow 应该重新考虑应该将什么归类为意见。同时我明白,如果我问“Gradle Vs Maven,我应该选择哪一个”,这是一个寻求意见的问题。
  • 是的,我同意你的观点,问题是这条线很难划清……
  • 太累了,很多问题都被关闭了,因为它们是基于意见的。我喜欢意见,而且它们对我来说往往比事实更有价值(我可以在目标库/框架/等的源代码/文档中轻松找到)。这家伙只是需要帮助..
  • 就个人而言,我会投票支持 Danylo Zatorsky 的回答。 DTO 是真正的客户驱动和合同设计。在某些微服务模式中,例如聚合,DTO 将在不同的微服务中发挥重要作用。

标签: java spring web-services spring-mvc spring-boot


【解决方案1】:

我会投票支持使用 DTO,原因如下:

  • 不同的请求(事件)和您的数据库实体。通常情况下,您的请求/响应与域模型中的不同。特别是它在微服务架构中很有意义,在这种架构中,您有很多来自其他微服务的事件。例如,您有 Order 实体,但您从另一个微服务获得的事件是 OrderItemAdded。即使一半的事件(或请求)与实体相同,为所有事件(或请求)设置 DTO 以避免混乱仍然是有意义的。
  • 数据库架构与您公开的 API 之间的耦合。使用实体时,您基本上会公开如何在特定微服务中对数据库进行建模。在 MySQL 中,您可能希望您的实体具有关系,它们在组成方面将非常庞大。在其他类型的数据库中,您将拥有没有大量内部对象的平面实体。这意味着,如果您使用实体来公开您的 API 并希望将您的数据库从 MySQL 更改为 Cassandra - 您还需要更改您的 API,这显然是一件坏事。
  • Consumer Driven Contracts。可能这与上一个项目符号有关,但是 DTO 可以更容易地确保微服务之间的通信在它们的演变过程中不会中断。因为合约和数据库没有耦合,所以更容易测试。
  • 聚合。有时您需要返回比单个数据库实体中更多的东西。在这种情况下,您的 DTO 将只是一个聚合器。
  • 性能。微服务意味着通过网络传输大量数据,这可能会导致性能问题。如果您的微服务客户端需要的数据少于您存储在数据库中的数据 - 您应该为他们提供更少的数据。同样 - 只需创建一个 DTO,您的网络负载就会减少。
  • 忘记 LazyInitializationException。 与 ORM 管理的域实体相比,DTO 没有任何延迟加载和代理。
  • 使用正确的工具支持 DTO 层并不难。 通常,在将实体映射到 DTO 和向后映射时会出现问题 - 每次要进行转换时都需要手动设置正确的字段。在向实体和 DTO 添加新字段时,很容易忘记设置映射,但幸运的是,有很多工具可以为您完成这项任务。例如,我们曾经在我们的项目中使用 MapStruct - 它可以在编译时自动为您生成转换

【讨论】:

    【解决方案2】:

    只公开域对象的优点

    1. 您编写的代码越少,产生的错误就越少。
      • 尽管我们的代码库中有大量(有争议的)测试用例,但由于从域到 DTO 或反之亦然的字段复制遗漏/错误,我遇到了一些错误。
    2. 可维护性 - 更少的样板代码。
      • 如果我必须添加一个新属性,我当然不必添加 Domain、DTO、Mapper 和测试用例。不要告诉我这可以使用反射 beanCopy 工具来实现,它违背了整个目的。
      • Lo​​mbok、Groovy、Kotlin 我知道,但它只会让我省去 getter setter 的头痛。
    3. 干燥
    4. 性能
      • 我知道这属于“过早的性能优化是万恶之源”的范畴。但这仍然会节省一些 CPU 周期,因为不必为每个请求创建(以及以后的垃圾收集)一个对象(至少)

    缺点

    1. 从长远来看,DTO 将为您提供更大的灵活性
      • 如果我需要这种灵活性就好了。至少,到目前为止我遇到的任何事情都是通过 http 进行的 CRUD 操作,我可以使用几个 @JsonIgnores 来管理这些操作。或者如果有一个或两个字段需要使用 Jackson Annotation 无法完成的转​​换,正如我之前所说,我可以编写自定义逻辑来处理这个问题。
    2. 域对象因注释而变得臃肿。
      • 这是一个有效的问题。如果我使用 JPA 或 MyBatis 作为我的持久化框架,域对象可能有这些注释,那么也会有 Jackson 注释。就我而言,这不太适用,我使用的是 Spring Boot,我可以通过使用应用程序范围的属性(如 mybatis.configuration.map-underscore-to-camel-case: truespring.jackson.property-naming-strategy: SNAKE_CASE

    短篇小说,至少在我的情况下,缺点并没有超过优点,因此通过将新的 POJO 作为 DTO 来重复我自己是没有任何意义的。更少的代码,更少的错误机会。因此,继续公开 Domain 对象而不是单独的“视图”对象。

    免责声明:这可能适用于您的用例,也可能不适用。这个观察是根据我的用例(基本上是一个有 15 个端点的 CRUD api)

    【讨论】:

    • 对于“领域模型”贫乏的 CRUD 服务,我完全同意你的看法。使用丰富的复杂域模型,这有点困难。所以在我看来,这是间接的,在微服务环境中,可能会因服务、上下文、上下文而异。
    【解决方案3】:

    如果您使用 CQRS,这个决定要简单得多,因为:

    • 对于写入端,您使用已经是 DTO 的 CommandsAggregates - 你的领域层中的丰富行为对象 - 没有暴露/查询,所以那里没有问题。
    • 对于读取端,因为您使用的是薄层,所以从持久性中获取的对象应该已经是 DTO。应该没有映射问题,因为每个用例都可以有一个readmodel。在最坏的情况下,您可以使用 GraphQL 之类的工具仅选择您需要的字段。

    如果您不将读取与写入分开,则做出决定会更加困难,因为这两种解决方案都需要权衡取舍。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-17
      • 2019-04-06
      • 2020-09-29
      • 1970-01-01
      • 2016-12-08
      • 2017-10-15
      • 1970-01-01
      相关资源
      最近更新 更多