GraphQL ロゴGraphQL

スキーマと型

このページでは、GraphQLの型システムとそのデータクエリ記述方法について、必要な情報をすべて学習します。GraphQLはあらゆるバックエンドフレームワークやプログラミング言語で使用できるため、実装固有の詳細には触れず、概念のみに焦点を当てて説明します。

型システム#

GraphQLクエリを以前に見たことがある場合は、GraphQLクエリ言語は基本的にオブジェクト上のフィールドを選択することであることをご存知でしょう。そのため、次のクエリでは

  1. 特別な「ルート」オブジェクトから開始します。
  2. そのオブジェクトのheroフィールドを選択します。
  3. heroによって返されるオブジェクトに対して、nameappearsInフィールドを選択します。

GraphQLクエリの形状は結果と密接に一致するため、サーバーについて詳しく知らなくても、クエリが返すものを予測できます。しかし、要求できるデータの正確な記述(選択できるフィールド、返される可能性のあるオブジェクトの種類、それらのサブオブジェクトで使用できるフィールドなど)があると便利です。それがスキーマの役割です。

すべてのGraphQLサービスは、そのサービスでクエリできる可能性のあるデータセットを完全に記述する一連の型を定義します。その後、クエリが到着すると、そのスキーマに対して検証および実行されます。

型言語#

GraphQLサービスは任意の言語で記述できます。GraphQLスキーマについて説明するために、JavaScriptのような特定のプログラミング言語の構文に依存できないため、独自のシンプルな言語を定義します。「GraphQLスキーマ言語」を使用します。これはクエリ言語に似ており、言語に依存しない方法でGraphQLスキーマについて説明できます。

オブジェクト型とフィールド#

GraphQLスキーマの最も基本的なコンポーネントはオブジェクト型です。これは、サービスからフェッチできるオブジェクトの種類とそのフィールドを表します。GraphQLスキーマ言語では、次のように表すことができます。

type Character {
name: String!
appearsIn: [Episode!]!
}

言語は非常に読みやすいですが、共通の語彙を持つために確認しましょう。

  • CharacterGraphQLオブジェクト型です。つまり、いくつかのフィールドを持つ型です。スキーマ内の型のほとんどはオブジェクト型です。
  • nameappearsInCharacter型のフィールドです。つまり、nameappearsInは、Character型を操作するGraphQLクエリの任意の部分に表示できる唯一のフィールドです。
  • Stringは組み込みのスカラー型の1つです。これらは単一のスカラーオブジェクトに解決される型であり、クエリでサブセレクションを持つことはできません。スカラー型については後で詳しく説明します。
  • String!は、フィールドが非NULLであることを意味します。つまり、GraphQLサービスは、このフィールドをクエリすると常に値を提供することを約束します。型言語では、感嘆符を使用して表します。
  • [Episode!]!Episodeオブジェクトの配列を表します。これも非NULLであるため、appearsInフィールドをクエリするときは常に(0個以上のアイテムを含む)配列を期待できます。また、Episode!非NULLであるため、配列の各アイテムがEpisodeオブジェクトであることを常に期待できます。

これで、GraphQLオブジェクト型の外観とGraphQL型言語の基本的な読み方について理解できました。

引数#

GraphQLオブジェクト型のすべてのフィールドには、0個以上の引数を持つことができます。たとえば、次のlengthフィールド。

type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}

すべての引数には名前が付けられています。関数が順序付けられた引数のリストを受け取るJavaScriptやPythonなどの言語とは異なり、GraphQLのすべての引数は名前で具体的に渡されます。この場合、lengthフィールドには、unitという1つの定義済みの引数があります。

引数は必須またはオプションのいずれかです。引数がオプションの場合、デフォルト値を定義できます。unit引数が渡されない場合、デフォルトでMETERに設定されます。

Query型とMutation型#

スキーマ内のほとんどの型は通常のオブジェクト型ですが、スキーマ内で特別な2つの型があります。

schema {
query: Query
mutation: Mutation
}

すべてのGraphQLサービスにはquery型があり、mutation型がある場合とない場合があります。これらの型は通常のオブジェクト型と同じですが、すべてのGraphQLクエリのエントリポイントを定義するため特別です。そのため、次のようなクエリを見ると

GraphQLサービスには、herodroidフィールドを持つQuery型が必要です。

type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}

Mutationは同様の方法で機能します。Mutation型にフィールドを定義すると、それらはクエリで呼び出すことができるルートmutationフィールドとして使用できます。

「エントリポイント」であるという特別なステータス以外では、Query型とMutation型は他のGraphQLオブジェクト型と同じであり、それらのフィールドはまったく同じように機能することを覚えておくことが重要です。

スカラー型#

GraphQLオブジェクト型には名前とフィールドがありますが、ある時点でそれらのフィールドは具体的なデータに解決される必要があります。そこでスカラー型が登場します。これらはクエリのリーフを表します。

次のクエリでは、nameappearsInフィールドはスカラー型に解決されます。

これらのフィールドにはサブフィールドがないため、クエリのリーフであることがわかります。

GraphQLには、すぐに使用できるデフォルトのスカラー型のセットが用意されています。

  • Int:符号付き32ビット整数。
  • Float:符号付き倍精度浮動小数点値。
  • String:UTF‐8文字列。
  • Booleantrueまたはfalse
  • ID:IDスカラー型は一意の識別子を表現し、多くの場合、オブジェクトを再フェッチするため、またはキャッシュのキーとして使用されます。ID型はStringと同じ方法でシリアル化されますが、IDとして定義すると、人間が読み取ることを意図していないことが示されます。

ほとんどのGraphQLサービス実装では、カスタムスカラー型を指定する方法もあります。たとえば、Date型を定義できます。

scalar Date

次に、その型のシリアル化、デシリアル化、検証方法を実装で定義します。たとえば、Date型は常に整数タイムスタンプにシリアル化されるように指定し、クライアントはすべての日付フィールドでその形式を期待するようにする必要があります。

列挙型#

Enumとも呼ばれる列挙型は、特定の許可値セットに制限された特殊なスカラー型です。これにより、以下を行うことができます。

  1. この型の引数が許可された値の1つであることを検証する
  2. 型システムを通じて、フィールドが常に有限の値セットの1つであることを伝える

GraphQLスキーマ言語でのenum定義の例を次に示します。

enum Episode {
NEWHOPE
EMPIRE
JEDI
}

これは、スキーマでEpisode型を使用する場合は常に、NEWHOPEEMPIRE、またはJEDIのいずれか1つであることを意味します。

さまざまな言語のGraphQLサービス実装には、enumを処理するための独自の言語固有の方法があることに注意してください。enumをファーストクラスの市民としてサポートする言語では、実装はその機能を利用する可能性があります。enumをサポートしないJavaScriptのような言語では、これらの値は内部的に整数のセットにマッピングされる可能性があります。ただし、これらの詳細はクライアントには漏れません。クライアントはenum値の文字列名で完全に動作できます。

リストと非NULL#

オブジェクト型、スカラー、enumは、GraphQLで定義できる唯一の種類の型です。しかし、スキーマの他の部分やクエリの変数宣言で使用する場合、それらの値の検証に影響を与える追加の型修飾子を適用できます。例を見てみましょう。

type Character {
name: String!
appearsIn: [Episode]!
}

ここでは、String型を使用し、型名の後に感嘆符!を追加することで、非NULLとしてマークしています。これは、サーバーがこのフィールドに対して常に非NULL値を返すことを期待しており、最終的にNULL値を取得すると、実際にGraphQL実行エラーが発生し、クライアントに何かが間違っていることを知らせることを意味します。

非NULL型修飾子は、フィールドの引数を定義する場合にも使用でき、GraphQL文字列または変数のいずれかでNULL値が引数として渡された場合、GraphQLサーバーは検証エラーを返します。

リストは同様の方法で機能します。型修飾子を使用して型をListとしてマークできます。これは、このフィールドがその型の配列を返すことを示します。スキーマ言語では、これは型を角括弧[]で囲むことで示されます。引数の場合も同様に機能し、検証ステップではその値に配列が期待されます。

非NULLとListの修飾子は組み合わせることができます。たとえば、非NULL文字列のリストを持つことができます。

myField: [String!]

これは、リスト自体がNULLになる可能性がありますが、NULLメンバーを持つことはできないことを意味します。たとえば、JSONでは

myField: null // valid
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // error

ここで、非NULL文字列のリストを定義したとします。

myField: [String]!

これは、リスト自体がNULLになることはできませんが、NULL値を含むことができることを意味します。

myField: null // error
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // valid

必要に応じて、任意の数の非NULLとListの修飾子を自由にネストできます。

インターフェース#

多くの型システムと同様に、GraphQLはインターフェースをサポートしています。インターフェースは、型がインターフェースを実装するために含める必要がある特定のフィールドセットを含む抽象型です。

たとえば、スターウォーズ三部作の任意のキャラクターを表すCharacterインターフェースを持つことができます。

interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}

これは、Characterを実装する型には、これらの引数と戻り値型を持つこれらの正確なフィールドが含まれている必要があることを意味します。

たとえば、Characterを実装する可能性のある型をいくつか次に示します。

type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}

これらの2つの型にはどちらもCharacterインターフェースのすべてのフィールドがありますが、その特定のキャラクターの種類に固有のtotalCreditsstarshipsprimaryFunctionなどの追加フィールドも含まれています。

オブジェクトまたはオブジェクトセットを返す必要があるが、それらがいくつかの異なる型である可能性がある場合に、インターフェースは役立ちます。

たとえば、次のクエリはエラーを生成することに注意してください。

heroフィールドはCharacter型を返し、これはepisode引数に応じてHumanまたはDroidのいずれかである可能性があることを意味します。上記のクエリでは、Characterインターフェースに存在するフィールドのみを要求できますが、primaryFunctionは含まれていません。

特定のオブジェクト型のフィールドを要求するには、インラインフラグメントを使用する必要があります。

クエリガイドのインラインフラグメントセクションで詳細を確認してください。

ユニオン型#

ユニオン型はインターフェースと類似性がありますが、構成要素の型間で共有フィールドを定義する機能がありません。

union SearchResult = Human | Droid | Starship

スキーマでSearchResult型を返す場合は常に、HumanDroid、またはStarshipを取得する可能性があります。ユニオン型のメンバーは具体的なオブジェクト型である必要があることに注意してください。インターフェースや他のユニオンからユニオン型を作成することはできません。

この場合、SearchResult ユニオン型を返すフィールドをクエリする場合は、いかなるフィールドもクエリできるようにインラインフラグメントを使用する必要があります。

__typename フィールドはString に解決され、クライアント上で異なるデータ型を区別することができます。

また、この場合、HumanDroid は共通のインターフェース(Character)を共有しているため、複数の型にわたって同じフィールドを繰り返す必要なく、一箇所で共通のフィールドをクエリできます。

{
search(text: "an") {
__typename
... on Character {
name
}
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
name
length
}
}
}

StarshipCharacter ではないため、name を指定しないと結果に表示されないことに注意してください!

入力型#

これまで、列挙型や文字列などのスカラー値を引数としてフィールドに渡すことについてのみ説明してきました。しかし、複雑なオブジェクトも簡単に渡すことができます。これは、作成するオブジェクト全体を渡したい可能性のあるミューテーションの場合に特に役立ちます。GraphQL スキーマ言語では、入力型は通常のオブジェクト型とまったく同じように見えますが、type の代わりにキーワード input を使用します。

input ReviewInput {
stars: Int!
commentary: String
}

ミューテーションで入力オブジェクト型を使用する方法を次に示します。

入力オブジェクト型のフィールドは、それ自体が入力オブジェクト型を参照できますが、スキーマ内で入力型と出力型を混在させることはできません。入力オブジェクト型は、フィールドに引数を持つこともできません。

続きを読む →バリデーション