微服务Restful API - DTO或不?

时间:2017-06-15 16:06:39

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

REST API - DTOs or not?

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

  

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

     

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

现在,我的问题

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

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

3 个答案:

答案 0 :(得分:15)

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

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

答案 1 :(得分:3)

公开域对象的优点

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

    1. 从长远来看,DTO将为您提供更大的灵活性
      • 如果我只需要那种灵活性。至少,到目前为止我遇到的是对http的CRUD操作,我可以使用@JsonIgnores来管理。或者,如果有一个或两个字段需要使用Jackson Annotation无法完成的转​​换,正如我之前所说,我可以编写自定义逻辑来处理这个问题。
    2. 域对象因注释而变得臃肿。
      • 这是一个有效的问题。如果我使用JPA或MyBatis作为我的持久框架,域对象可能会有这些注释,那么也会有Jackson注释。在我的情况下,这不太适用,但我使用Spring启动,我可以通过使用应用程序范围的属性,如mybatis.configuration.map-underscore-to-camel-case: truespring.jackson.property-naming-strategy: SNAKE_CASE
      • 来逃避
    3. 短篇小说,至少在我的情况下,缺点并不比专业人士好,所以通过新的POJO作为DTO重复自己没有任何意义。代码减少,错误机会减少。因此,继续公开Domain对象,而不是单独的"视图"对象

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

答案 2 :(得分:1)

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

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

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