web-dev-qa-db-ja.com

REST API:「部分的に」ネストされたオブジェクトを処理する

名前と生徒のリストを含むいくつかのデータを持つ学校と、彼らが登録しているコースや学校への参照を含むいくつかのデータを持つ生徒がいるとしましょう。クライアント上:

  • すべての生徒の名前のリストを含む、学校に関する情報を示す画面を表示したいのですが。
  • 生徒の学校名や受講しているコースの名前など、生徒に関する情報を示す画面を表示したいのですが。
  • 新しいフェッチを待たずに同じ画面を表示できるように、この情報をキャッシュしたいと思います。学校に行かなくても、学校から生徒、そして学校に戻ることができるはずです。
  • 1つのフェッチのみで各画面を表示したいと思います。学校のページから生徒のページに移動すると、個別に取得できますが、1つの取得で生徒の名前の完全なリストを学校に表示できるはずです。
  • データの重複を避けたいので、学校名が変更された場合、学校を更新するために1回フェッチすると、学校ページと生徒ページの両方に正しい名前が表示されます。

これをすべて行う良い方法はありますか、それともいくつかの制約を解除する必要がありますか?

最初のアプローチは、次のようなAPIを持つことです。

GET /school/1

{
    id: 1,
    name: "Jefferson High",
    students: [
        {
             id: 1
             name: "Joel Kim"
        },
        {
             id: 2,
             name: "Chris Green"
        }
        ...
    ]
}


GET /student/1

{
    id: 1,
    name: "Joel Kim",
    school: {
        id: 1,
        name: "Jefferson High"
    }
    courses: [
        {
             id: 3
             name: "Algebra 1"
        },
        {
             id: 5,
             name: "World History"
        }
        ...
    ]
}

このアプローチの利点は、画面ごとに単一のフェッチを実行できることです。クライアント側では、学校と生徒がIDで相互に参照するように正規化し、オブジェクトを異なるデータストアに格納できます。ただし、studentの内側にネストされているschoolオブジェクトは完全なオブジェクトではなく、ネストされたコースや学校への参照は含まれていません。同様に、school内のstudentオブジェクトには、出席しているすべての学生のリストがありません。オブジェクトの部分的な表現をデータストアに格納すると、クライアント側で一連の複雑なロジックが発生します。

これらのオブジェクトを正規化する代わりに、入れ子になった部分オブジェクトを使用して学校と生徒を保存できます。ただし、これはデータの重複を意味します。JeffersonH​​ighの各生徒には、学校の名前がネストされます。特定の生徒のフェッチを実行する直前に学校名が変更された場合、その生徒には正しい学校名が表示されますが、「学校の詳細」ページを含む他の場所では間違った名前が表示されます。

ネストされたオブジェクトのIDを返すだけのAPIを設計することもできます。

GET /school/1

{
    id: 1,
    name: "Jefferson High",
    students: [1, 2]
}


GET /student/1

{
    id: 1,
    name: "Joel Kim",
    school: 1,
    courses: [3, 5]
}

すべての参照を含むオブジェクトの「完全な」表現は常にあるので、この情報をデータストアのクライアント側に格納するのは非常に簡単です。ただし、これには各画面を表示するために複数のフェッチが必要になります。生徒に関する情報を表示するには、生徒を取得してから、学校とコースを取得する必要があります。

各オブジェクトのコピーを1つだけキャッシュし、基本的な画面を表示するための複数のフェッチを防ぐことができるよりスマートなアプローチはありますか?

6
Michael Marvick

RESTでは、別のリソースとしてリストしたすべてを見ることができます。それは別のアプローチです。一般的に、RESTリソース内のデータを非正規化(データベース用語)して、ユースケースに合わせます。したがって、サンプルは問題なく、一度に必要なすべてのデータが含まれているため、そのためのクライアントを簡単に構築できます。

RESTでは、次のリソースがあると言えます:*/schools(学校のリスト)*/schools/1(学生のリストがある学校)*/students(おそらく学生のリスト)*/students/1(たとえば、コースのリストを持つ学生)。 */courses(すべてのコースのリスト)*/courses/1(登録されているすべての学生を含むことができる1つのコースのリスト)

さらに、次のこともできます。*/schools/1/students(この学校のすべての学生のリスト)*/students/1/courses(この学生のすべてのコース)*/students/1/courses-finished(すべての終了したコース)この学生のために)

各オブジェクトのコピーを1つだけキャッシュし、基本的な画面を表示するための複数のフェッチを防ぐことができるよりスマートなアプローチはありますか?

RESTのObject!=リソース。たとえば学校のリストもリソースです。したがって、ここには3つのデータベーステーブルがありますが、複数のリソースがあります。

クライアント側のデータストアクライアント上の個別のデータストアにデータを格納することは、別の問題です。 Meteorに実装されていることはわかっています。 https://meteorhacks.com/understanding-mergebox/ これにより、必要に応じて展開する最初の小さいドキュメントを作成できます。

Subscriptionsつまり、あなたは学校と学生を購読します(リストに必要な情報のみ、たとえばIDと名前など)。次に、詳細を確認したい生徒がわかったら、その生徒を完全にサブスクライブします。舞台裏では、特定の学生のすべての詳細をクライアント側のデータベースに入力します。

[〜#〜] rest [〜#〜]これはRESTに関するものではなく、まったく異なる作業方法です。 RESTを使用すると、通常、クライアント上のリソースを関連付ける必要はありません。

正規化

これらのオブジェクトを正規化する代わりに、入れ子になった部分オブジェクトを使用して学校と生徒を保存できます。ただし、これはデータの重複を意味します。JeffersonH​​ighの各生徒には、学校の名前がネストされます。

いいえ。RESTリソース(school/student/etc)は、データベースやその他のものとは異なります。 ( http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-74 )RESTリソーススクールは、背後で結合クエリを作成します。 RESTはそのことを考慮し、考慮すべきではありません。パフォーマンスが問題になる場合は、そのクエリをバックグラウンドで非正規化しても、RESTリソースは同じように見えます。データベースを切り替えても、リソースは同じに見えます。別の学生管理システムに切り替えても、リソースは変わりません。これは、RESTを使用することで得られる大きな利点です。もともとは、多くの未知のシステムを接続するように設計されていましたが、バックエンドテクノロジーにリンクされていないユニバーサルAPIを作成することで、一緒に作業することができます。

Caching学校名が変更された場合、その学校名を使用しているすべてのキャッシュを無効にする必要があります。 ETAGを使用してそれを行うことができます。これを理解する非常に単純な例は次のとおりです。 http://fideloper.com/api-etag-conditional-get

したがって、結合を使用して、学生名のリストを含む学校のリソースを生成するとします。そして、学校と生徒のテーブルの両方にmodifiedAtフィールドがあると仮定します。

リソースに両方のmodifiedAtフィールドを含めて(または少なくともそれらを使用して)、一意のETAGを生成すると、次のようになります。

  • 学校名が更新されると、ETAGは1つの学校とその学校のすべての生徒に対して無効になります。

  • 生徒名が更新された場合:ETAGは、生徒がいるすべての学校と1人の生徒(変更する生徒)から無効になります。

これにより、すべてのキャッシュがいつキャッシュを無効にするかを知ることができます。これらのリソースを事前にキャッシュすることもできます。 ETAGは、キャッシュされたインスタンスを返すことができます。したがって、ユーザーがオフラインになる場合は、ローカルリソースを使用できます。たとえば、ログイン後に、このユーザーのバックグラウンドで要求することにより、すべてのリソースをロードできます。

/students/1
/student/1/courses (even all filtered courses)
And then all courses he is enrolled into.

その後、それらはローカルキャッシュに置かれ、使用可能になります。これは楽観的ロードにも使用されます。キャッシュからすぐに表示することができます。次に、バックグラウンドでそれらが更新されているかどうかを確認します。それらが画面上のデータをリロードする場合。

複数のキャッシュがあることを理解してください。サーバーは1つのキャッシュを使用する場合があります。その間、一部のキャッシュがアクティブになる可能性があります。ブラウザキャッシュ、独自のコードがキャッシュする場合があります。この普遍的な方法により、ほとんどすべてのHTTPキャッシュが思い通りに動作します。

[〜#〜] hateoas [〜#〜]HATEOSについて読んでください。HATEOSを使用すると、生徒を簡単に取得できます。学校のリソースにそれらへのURLを含めるだけです。その後、リンクをたどることができます。例: https://www.infoq.com/articles/webber-rest-workflow と例: https://developer.Paypal.com/docs/integration/direct/ Paypal-rest-payment-hateoas-links /

これにより、それらのサブリソースを取得することがより管理しやすくなります。

たとえば、学生のページに彼のすべてのコースへのリンクを含むリストビューを表示できます。そして上部に、彼のすべての終了したコースへのリンクを表示する場合があります(APIで利用可能な場合)。そして、そのビジネスロジックはすべてAPIによって処理されます。だからあなたのクライアントはただ行うことができます:

/ students/1(学生に関するすべてのデータを取得するため)

if(student.links.finished) {
  console.log(student.links.finished);
}

彼がコースを終了していない場合、たとえば、リンクをそのように非表示にすることができます。

これは次のように展開することもできます:

student.links.courses {
  latest: /students/1/new-courses
  finished: /students/1/courses-finished
  failed: /students/1/courses-failed
}

そうすれば、クライアントでforEachループを実行して、使用可能なすべてのフィルターを表示できます。

戦略もちろん、両方を混在させることができます。 REST APIを使用するようなマージボックス実装を記述できます。クライアント上でそのデータを再利用して、たとえばそれらに対して複雑な検索/フィルタリングを実行したい場合、それは価値があるかもしれません。これが、クライアント側のデータベースを持つ利点です。

一方、RESTリソースとして適切なデータを配信するだけの場合、そのデータをクライアント側でデータベースに変換してから読み取るという直接的な利点はありません。必要なものだけが含まれているため、元のリソースをそのまま使用できる場合。

したがって、正確なユースケースによってはトレードオフになります。それをシンプルに保つことができ、うまく機能するRESTリソースを使用する場合。そして、それは大きな利点であるシンプルです。

複雑なデータ検索などを行う必要がある場合は、ローカルクライアントデータベースが適しています。

6
Luc Franken