微服务 Restful API - DTO 与否?现在,我的问题仅公开域对象的优点缺点

REST API - DTO 与否?

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

我目前正在为一个项目创建一个REST-API,并且一直在阅读有关最佳实践的文章。许多人似乎反对DTO,只是简单地公开了领域模型,而另一些人似乎认为DTO(或用户模型或任何你想称之为它的东西)是不好的做法。就个人而言,我认为这篇文章很有意义。

但是,我也了解DTO的缺点,其中包含所有额外的映射代码,域模型可能与DTO对应物100%相同等等。

现在,我的问题

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

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


答案 1

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

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

答案 2

仅公开域对象的优点

  1. 您编写的代码越少,产生的错误就越少。
    • 尽管在我们的代码库中有大量(有争议的)测试用例,但由于从域到DTO或反之亦然的字段的丢失/错误复制,我遇到了错误。
  2. 可维护性 - 更少的样板代码。
    • 如果我必须添加一个新属性,当然,我不必添加Domain,DTO,Mapper和测试用例。不要告诉我,这可以使用反射beanCopy实用程序来实现,它违背了整个目的。
    • 龙目岛,Groovy,Kotlin我知道,但它只会让我免于头痛。
  3. 性能
    • 我知道这属于“过早的性能优化是万恶之源”的范畴。但这仍然会节省一些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)


推荐