web-dev-qa-db-ja.com

Spring Data RESTがRESTリソースを介してDTOを使用せずにリソースを公開することは問題ですか?

私の限られた経験では、エンティティをフロントエンドやREST経由で渡すのではなく、DTOを使用するように繰り返し言われています。

Spring Data Restはこれを正確に行いませんか?プロジェクションを簡単に調べましたが、これらは返されるデータを制限するだけで、データベースに保存するpostメソッドへのパラメーターとしてエンティティーを期待しています。ここに何か欠けているのでしょうか、それとも私(および私の同僚)は、あなたの周りに存在してはならず、実体化してはいけないという点で間違っていますか?

33
chartle

tl; dr

いいえ。DTOは、HTTPリソースで公開されている表現からサーバー側ドメインモデルを分離するための1つの手段にすぎません。また、Spring Data RESTが行うような、他の分離手段を使用することもできます。

細部

はい。SpringData RESTは、サーバー側にあるドメインモデルを検査して、公開するリソースの表現がどのように見えるかを推論します。ただし、ドメインオブジェクトの単純な公開がもたらす問題を軽減するいくつかの重要な概念が適用されます。

Spring Data RESTは、集約を探し、デフォルトでそれに応じて表現を形成します。

素朴な「ドメインオブジェクトをジャクソンの前に投げる」の根本的な問題は、プレーンエンティティモデルから、合理的な表現の境界を推測するのが非常に難しいことです。特に、データベーステーブルから派生したエンティティモデルは、事実上すべてのものをすべてのものに接続する傾向があります。これは、集計のような重要なドメインの概念がほとんどの永続化テクノロジに存在しないという事実に由来します(特にリレーショナルデータベースを参照)。

ただし、この場合、「ドメインモデルを公開しない」のほうが、問題の核心よりもその症状に対応していると私は主張します。ドメインモデルを適切に設計すると、ドメインモデルのメリットと、状態の変化を通じてそのモデルを効果的に駆動するための適切な表現との間に大きなオーバーラップがあります。いくつかの簡単なルール:

  • 別のエンティティとのすべての関係について、自問してみてください。これはむしろid参照ではあり得ません。オブジェクト参照を使用することにより、関係の反対側の多くのセマンティクスをエンティティーに取り込みます。これを誤ると、通常、エンティティがエンティティを参照することになります。これは、より深いレベルでの問題です。表現レベルでは、これにより、データをカットオフしたり、整合性スコープに対応したりできます。
  • 双方向の関係は、更新側で正しく理解することが難しいことで悪名高いため、避けてください。

Spring Data RESTは、これらのエンティティの関係をHTTPレベルの適切なメカニズムに実際に転送するためにかなりの数の処理を実行します。リンク一般、さらに重要なのは、これらの関係を管理する専用リソースへのリンクです。これは、エンティティーに対して宣言されたリポジトリーを検査することによって行われ、基本的に、関連するエンティティーのその他の必要なインライン化を、その関係を明示的に管理できる関連付けリソースへのリンクに置き換えます。

このアプローチは、通常、HTTPレベルでDDDアグリゲートによって記述される一貫性保証とうまく機能します。 PUTリクエストは、デフォルトでは複数のアグリゲートにまたがることはありません。これは、ドメインの概念と一致するリソースの整合性の範囲を意味するため、これは良いことです。

DTOがドメインオブジェクトのフィールドを複製するだけの場合は、ユーザーにDTOを強制する意味はありません。

ドメインオブジェクトのDTOはいくつでも導入できます。ほとんどの場合、ドメインオブジェクトにキャプチャされたフィールドは、何らかの形で表現に反映されます。エンティティCustomerfirstnamelastname、およびemailAddressプロパティが含まれており、それらが表現と完全に無関係であることはまだわかりません。

DTOの導入は、決して分離を保証するものではありません。貨物養殖の理由で導入されたプロジェクトが多すぎて、それらを支えるエンティティのすべてのフィールドを複製しただけで、新しいフィールドもすべてDTOに追加する必要があったため、追加の作業が発生しただけです。しかし、ちょっと、デカップリング!ない。 ¯\ _(ツ)_ /¯

そうは言っても、特に強く型付けされた値オブジェクトを使用する場合など、これらのプロパティの表現を少し微調整したい場合もあります。 EmailAddress(良い!)でも、これをJSONでプレーンなStringとしてレンダリングしたい。しかし、それが問題であることは決してありません。SpringData RESTは、さまざまな方法で表現を微調整する手段を提供するJacksonを使用しています—アノテーション、ミックスイン、ドメインタイプの外部にアノテーションを保持するためのカスタムシリアライザなどの間にマッピング層があります。

デフォルトでDTOを使用しないこと自体は悪いことではありません。すべてに対してDTOを作成する必要がある場合に必要なボイラープレートの量について、ユーザーからの抗議を想像してみてください。 DTOは単にoneで終わりを意味します。その目的を別の方法で達成できる場合(通常は可能です)、なぜDTOを主張するのでしょうか。

要件に合わない場合は、Spring Data RESTを使用しないでください。

カスタマイズの作業を続けると、APIの基本的なREST AP​​I実装パターンに従うAPIの部分を正確にカバーするためにSpring Data RESTが存在することに注目する価値があります。そしてその機能は、考える時間を増やすために用意されています。

  1. ドメインモデルを形成する方法
  2. ハイパーメディア主導の相互作用により、APIのどの部分がより適切に表現されるか。

これは、SpringOne Platform 2016で行った講演のスライドで、状況をまとめたものです。

What makes up a REST API built with Spring Data REST

完全なスライドデッキが見つかります ここ 。 InfoQでは トークの録音 も利用できます。

Spring Data RESTは、下線の付いた円に集中できるようにするためのものです。 Spring Data RESTをオンにするだけで、すばらしい本当にAPIを構築できるとは決して考えていません。おもしろいことを考える時間を増やすために、ボイラープレートの量を減らしたいだけです。

Spring Dataと同様に、一般に、標準の永続化操作用に作成するボイラープレートコードの量を削減します。実際にCRUD操作だけで実際のアプリを構築できると主張する人は誰もいません。しかし、退屈な部分から努力を取り除いて、実際のドメインの課題についてより集中的に考えることができます(そして、実際にそれを行う必要があります:))。

必要に応じてドメインタイプをDT​​Oに手動でマッピングするなど、特定のリソースをオーバーライドしてその動作を完全に制御することもできます。また、Spring Data RESTが提供する機能の横にカスタム機能を配置して、2つをフックすることもできます。何を使用するかを慎重に選択してください。

サンプル

RESTful Webサービスの本にあるRESTBucksサンプルのSpring(データREST)ベースの実装である Spring RESTBucks で説明したものの少し高度な例を見つけることができます。 Spring Data RESTを使用してOrderインスタンスを管理しますが、その処理を微調整してカスタム要件を導入し、ストーリーの支払い部分を手動で完全に実装します。

55
Oliver Drotbohm

Spring Data RESTを使用すると、データベース構造に基づいてプロトタイプを作成し、REST APIを作成する非常に高速な方法が可能になります。他のプログラミング技術と。

その代償として、REST APIがデータベース構造に密接に結合されています。それが大きな問題である場合もあります。そうでない場合もあります。基本的にはデータベース設計の品質に依存しますAPIユーザーのニーズに合わせて変更する機能。

要するに、Spring Data RESTは、特定の特別な状況下で多くの時間を節約できるツールであり、あらゆる問題に適用できる特効薬ではありません。

3
Andres

以前は、プロジェクト内のすべてのエンティティに完全に従来のレイヤー(データベース、DTO、リポジトリ、サービス、コントローラーなど)を含むDTOを使用していました。 DTOがいつか私たちの命を救うことを願っています:)

したがって、_id,name,country,state_を含む単純なCityエンティティの場合、次のようにしました。

  1. Cityテーブル、_id,name,county,...._列
  2. CityDTO with _id,name,county,...._プロパティ(データベースとまったく同じ)
  3. CityRepositoryfindCity(id),....
  4. CityService with findCity(id) { CityRepository.findCity(id) }
  5. CityController with findCity(id) { ConvertToJson( CityService.findCity(id)) }

都市情報をクライアントに公開するだけの定型コードが多すぎます。これは単純なエンティティであるため、これらのレイヤーに沿ってビジネスは行われず、オブジェクトが通過するだけです。 Cityエンティティの変更はデータベースから始まり、すべてのレイヤーが変更されました。 (たとえば、最後にlocationプロパティをlocationとしてユーザーに公開する必要があるため、jsonプロパティを追加します)。 findByNameAndCountryAllIgnoringCaseメソッドを追加するには、すべてのレイヤーを変更する必要があります(各レイヤーには新しいメソッドが必要です)。

Spring Data Rest(_of course with Spring Data_)を考えると、これは簡単ではありません!

_public interface CityRepository extends CRUDRepository<City, Long> {
   City findByNameAndCountryAllIgnoringCase(String name, String country);
}
_

cityエンティティは、最小限のコードでクライアントに公開されますが、都市の公開方法を制御できます。 ValidationSecurity、_Object Mapping_ ...そこにすべてあります。だからあなたはすべてのものを微調整することができます。

たとえば、cityエンティティプロパティ名の変更(レイヤー分離)をクライアントに知らせないようにしたい場合は、言及したカスタムオブジェクトマッパーを使用できます https://docs.spring.io/spring- data/rest/docs/3.0.2.RELEASE/reference/html /#customizing-sdr.custom-jackson-deserialization

要約するには

可能な限りSpring Data Restを使用します。複雑なユースケースでも、従来の階層化を行って、ServiceControllerにビジネスを任せることができます。

1
Alireza Fattahi