繁体   English   中英

微服务 Restful API - DTO 与否?

[英]Microservices Restful API - DTOs or not?

REST API - DTO 与否?

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

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

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

现在,我的问题

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

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

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

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

仅公开领域对象的优点

  1. 你写的代码越少,你产生的错误就越少。
    • 尽管我们的代码库中有大量(有争议的)测试用例,但我遇到了由于从域到 DTO 或反之亦然的字段的遗漏/错误复制而导致的错误。
  2. 可维护性 - 更少的样板代码。
    • 如果我必须添加一个新属性,我当然不必添加域、DTO、Mapper 和测试用例。 不要告诉我这可以使用反射 beanCopy utils 来实现,它违背了整个目的。
    • Lombok、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: true例如mybatis.configuration.map-underscore-to-camel-case: true , spring.jackson.property-naming-strategy: SNAKE_CASE

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

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

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

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

如果您不将读取与写入分开,那么决策将更加困难,因为这两种解决方案都需要权衡。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM