一貫性のあるオブジェクトアクセスにより、シンプルなキャッシングとオブジェクトルックアップが可能になります
GraphQLクライアントがキャッシングとデータの再取得を適切に処理するためのオプションを提供するために、GraphQLサーバーは標準化された方法でオブジェクト識別子を公開する必要があります。
これが機能するためには、クライアントはIDでオブジェクトをリクエストするための標準的なメカニズムを介してクエリを実行する必要があります。次に、レスポンスで、スキーマはこれらのIDを提供するための標準的な方法を提供する必要があります。
ID以外のオブジェクトに関する情報はほとんどないため、これらのオブジェクトを「ノード」と呼びます。ノードのクエリ例を以下に示します。
{ node(id: "4") { id ... on User { name } }}
node
フィールドを介して任意のオブジェクトを取得できるようにフォーマットされています。これは、「ノード」インターフェースに準拠するオブジェクトを返します。id
フィールドはレスポンスから安全に抽出でき、キャッシングと再取得を介して再利用するために保存できます。ノードインターフェースは次のようになります。
# An object with a Globally Unique IDinterface Node { # The ID of the object. id: ID!}
ユーザーは以下を介して準拠します。
type User implements Node { id: ID! # Full name name: String!}
以下では、サーバー実装全体の一貫性を確保するために準拠するオブジェクト識別に関する仕様を、より正式な要件で説明します。これらの仕様は、サーバーがRelay APIクライアントに準拠する方法に基づいていますが、どのクライアントにも役立ちます。
この仕様と互換性のあるGraphQLサーバーは、一貫性のあるオブジェクト識別モデルをサポートするために、特定のタイプとタイプ名を予約する必要があります。特に、この仕様では、以下のタイプに関するガイドラインを作成します。
Node
という名前のインターフェース。node
フィールド。サーバーは、Node
と呼ばれるインターフェースを提供する必要があります。そのインターフェースには、null以外の ID
を返す id
と呼ばれるフィールドが1つだけ含まれている必要があります。
この id
は、このオブジェクトのグローバルに一意の識別子である必要があり、この id
だけが与えられれば、サーバーはオブジェクトを再取得できる必要があります。
上記のインターフェースを正しく実装するサーバーは、次のイントロスペクションクエリを受け入れ、提供されたレスポンスを返します。
{ __type(name: "Node") { name kind fields { name type { kind ofType { name kind } } } }}
は以下を生成します。
{ "__type": { "name": "Node", "kind": "INTERFACE", "fields": [ { "name": "id", "type": { "kind": "NON_NULL", "ofType": { "name": "ID", "kind": "SCALAR" } } } ] }}
サーバーは、Node
インターフェースを返す node
と呼ばれるルートフィールドを提供する必要があります。このルートフィールドは、id
という名前のnull以外のIDを1つだけ引数として取る必要があります。
クエリが Node
を実装するオブジェクトを返す場合、サーバーによって Node
の id
フィールドで返された値が node
ルートフィールドに id
パラメーターとして渡されると、このルートフィールドは同一のオブジェクトを再取得する必要があります。
サーバーはこのデータの取得に最善を尽くす必要がありますが、常に可能とは限りません。たとえば、サーバーは有効な id
を持つ User
を返す場合がありますが、node
ルートフィールドを使用してそのユーザーを再取得するリクエストが行われたときに、ユーザーのデータベースが使用できないか、ユーザーがアカウントを削除している可能性があります。この場合、このフィールドのクエリ結果は null
である必要があります。
上記の要件を正しく実装するサーバーは、次のイントロスペクションクエリを受け入れ、提供されたレスポンスを含むレスポンスを返します。
{ __schema { queryType { fields { name type { name kind } args { name type { kind ofType { name kind } } } } } }}
は以下を生成します。
{ "__schema": { "queryType": { "fields": [ // This array may have other entries { "name": "node", "type": { "name": "Node", "kind": "INTERFACE" }, "args": [ { "name": "id", "type": { "kind": "NON_NULL", "ofType": { "name": "ID", "kind": "SCALAR" } } } ] } ] } }}
2つのオブジェクトがクエリに表示され、どちらも同じIDで `Node` を実装している場合、2つのオブジェクトは等しくなければなりません。
この定義のために、オブジェクトの等価性は次のように定義されます。
例えば
{ fourNode: node(id: "4") { id ... on User { name userWithIdOneGreater { id name } } } fiveNode: node(id: "5") { id ... on User { name userWithIdOneLess { id name } } }}
は以下を返す可能性があります
{ "fourNode": { "id": "4", "name": "Mark Zuckerberg", "userWithIdOneGreater": { "id": "5", "name": "Chris Hughes" } }, "fiveNode": { "id": "5", "name": "Chris Hughes", "userWithIdOneLess": { "id": "4", "name": "Mark Zuckerberg" } }}
`fourNode.id` と `fiveNode.userWithIdOneLess.id` が同じであるため、上記の条件により、`fourNode.name` は `fiveNode.userWithIdOneLess.name` と同じであることが保証されます。実際、それは同じです。
ユーザーのユーザー名を受け取り、対応するユーザーを返す `username` という名前のルートフィールドを考えてみましょう。
{ username(username: "zuck") { id }}
は以下を返す可能性があります
{ "username": { "id": "4" }}
明らかに、レスポンス内のオブジェクト(ID 4のユーザー)をリクエストにリンクし、オブジェクトをユーザー名「zuck」で識別できます。次に、ユーザー名のリストを受け取り、オブジェクトのリストを返す `usernames` という名前のルートフィールドを考えてみましょう。
{ usernames(usernames: ["zuck", "moskov"]) { id }}
は以下を返す可能性があります
{ "usernames": [ { "id": "4" }, { "id": "6" } ]}
クライアントがユーザー名をレスポンスにリンクできるようにするには、レスポンス内の配列が引数として渡された配列と同じサイズであり、レスポンス内の順序が引数内の順序と一致することを知る必要があります。これらを*複数識別ルートフィールド*と呼び、その要件を以下に示します。
この仕様に準拠したサーバーは、入力引数のリストを受け入れ、レスポンスのリストを返すルートフィールドを公開する場合があります。仕様に準拠したクライアントがこれらのフィールドを使用するには、これらのフィールドは*複数識別ルートフィールド*であり、次の要件に従う必要があります。
注:仕様に準拠したサーバーは、*複数識別ルートフィールド*ではないルートフィールドを公開する場合があります。仕様に準拠したクライアントは、それらのフィールドをクエリのルートフィールドとして使用できません。
*複数識別ルートフィールド*には、単一の引数が必要です。その引数のタイプは、null以外のnull以外のリストである必要があります。 `usernames` の例では、フィールドは `usernames` という名前の単一の引数を取り、そのタイプ(タイプシステムの省略形を使用)は `[String!]!` になります。
*複数識別ルートフィールド*の戻り値の型は、リスト、またはリストを囲むnull以外のラッパーである必要があります。リストは、`Node` インターフェース、`Node` インターフェースを実装するオブジェクト、またはそれらのタイプを囲むnull以外のラッパーをラップする必要があります。
*複数識別ルートフィールド*が使用されるたびに、レスポンス内のリストの長さは引数内のリストの長さと等しくなければなりません。レスポンス内の各項目は、入力内の対応する項目に対応する必要があります。より正式には、ルートフィールドに入力リスト `Lin` を渡すと出力値 `Lout` が生成された場合、任意の順列 `P` について、ルートフィールド `P(Lin)` を渡すと出力値 `P(Lout)` が生成される必要があります。
このため、サーバーはレスポンスタイプがnull以外のラッパーをラップしないようにすることをお勧めします。これは、入力内の特定のエントリのオブジェクトを取得できない場合でも、その入力エントリの出力に値を提供する必要があるためです。 `null` は、そうするための便利な値です。