簡體   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