问题 微服务Restful API - DTO或不?


REST API - DTO或不是?

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

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

但是,我也理解DTO的所有额外的缺点   映射代码,可能与其完全100%相同的域模型   DTO-pair等。

现在,我的问题

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

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


11413
2018-06-15 16:06


起源

与链接的问题一样,这完全是基于意见的(尽管这确实是一个非常好的问题) - Federico Peralta Schaffner
@FedericoPeraltaSchaffner感谢您的评论。我正在寻找更多的数据点/事实,而不是意见:)如果我无法从Stackoverflow平台得到这个问题的答案,我可能无法从其他任何地方得到它。恕我直言,Stackoverflow应该重新考虑应该被归类为意见的内容。同时我明白,如果我问,“Gradle Vs Maven,我应该选择哪一个”,它是一个意见寻求问题。 - so-random-dude
是的,我同意你的看法,问题是很难画出这条线...... - Federico Peralta Schaffner
令人厌倦的是,许多问题都是关闭的,因为它们是基于意见的。我喜欢意见,而且通常它们对我来说比事实更有价值(我可以在目标lib / framework /等的源/文档中轻松找到)。这家伙只需要帮助.. - Stanislav Bashkyrtsev
就个人而言,我会投票支持Danylo Zatorsky的回答。 DTO实际上是客户驱动的,由合同设计。在某些微服务模式中,如聚合,DTO将在不同的微服务中发挥重要作用。 - Allen Shi


答案:


我会投票使用DTO,这就是为什么:

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

12
2018-06-15 21:59





如果您使用CQRS,决定会更简单,因为:

  • 对于您使用的写入方 Commands 已经是DTO的; Aggregates  - 域层中的丰富行为对象 - 不会被公开/查询,因此没有问题。
  • 对于读取方,因为您使用了薄层,从持久性中提取的对象应该已经是DTO。应该没有映射问题,因为你可以有一个 readmodel 对于每个用例。在最坏的情况下,您可以使用GraphQL之类的东西来仅选择您需要的字段。

如果不将读取与写入分开,则决策更难,因为两种解决方案都存在权衡。


0
2018-06-17 02:49





公开域对象的优点

  1. 编写的代码越少,产生的错误就越少。
    • 尽管在我们的代码库中有广泛的(可论证的)测试用例,但由于错过/错误地将字段从域复制到DTO或反之,我遇到了错误。
  2. 可维护性 - 减少锅炉板代码。
    • 如果我必须添加一个新属性,我当然不必添加Domain,DTO,Mapper和testcases。不要告诉我这可以使用反射beanCopy utils来实现,它打败了整个目的。
    • 龙目岛,Groovy,Kotlin我知道,但它只会让我得到安慰者头痛。
  3. 性能
    • 我知道这属于“过早的性能优化是万恶之源”的范畴。但是这仍然可以节省一些CPU周期,而不必每次请求创建(以及后来的垃圾收集)一个或多个对象(至少)

缺点

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

短篇故事至少在我的情况下,缺点并不比专业人士好,所以通过新的POJO作为DTO重复自己是没有任何意义的。代码减少,错误机会减少。因此,继续公开Domain对象而没有单独的“view”对象。

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


0
2018-01-04 16:10



对于具有贫血“域模型”的CRUD服务,我完全同意您的看法。使用丰富的复杂域模型,这有点困难。因此,在我看来,它是间接的,并且在微服务环境中,它可能会因服务,服务,上下文而异。 - Nikolaj Dam Larsen