GraphQLの型システムを理解する
著者は、寄付を受ける団体として「Write for Donationsプログラム」の一環として「フリー・オープンソース基金」を選択しました。
はじめに
GraphQLは、フロントエンドとデータソースの間の通信を容易にするための現代的な解決策です。GraphQLスキーマには、GraphQL実装の詳細と機能がすべて明示されています。機能するGraphQLスキーマを作成するためには、GraphQLタイプシステムを理解する必要があります。
この記事では、GraphQLのタイプについて学びます。ビルトインのスカラータイプ(整数、浮動小数点数、文字列、真偽値、ID)、列挙型、リストやノンヌルのラッピングタイプ、オブジェクトタイプ、抽象インターフェースやユニオンタイプについて説明します。それぞれのタイプの例を確認し、それらを使用して完全なGraphQLスキーマを構築する方法を学びます。
前提条件
このチュートリアルを最大限に活用するためには、次のものが必要です。
- An understanding of the fundamental concepts of GraphQL, which are laid out in An Introduction to GraphQL.
- A GraphQL environment, an example of which can be found in How to Set Up a GraphQL API Server in Node.js.
スカラータイプ
GraphQLスキーマのすべてのデータは、最終的にプリミティブな値を表すさまざまなスカラータイプに解決されます。GraphQLのレスポンスは木で表現でき、スカラータイプはその木の末端にある葉となります。ネストされたレスポンスには多くのレベルが存在するかもしれませんが、最後のレベルは常にスカラータイプ(またはEnumタイプ)に解決されます。GraphQLにはInt、Float、String、Boolean、そしてIDの5つのビルトインスカラータイプが付属しています。
「Int」を日本語で自然な文に言い換えると:「インテリジェンス」
Intは、符号付き32ビットの非小数値です。小数点を含まない、符号(正または負)付きの整数です。符号付き32ビット整数の最大値は2,147,483,647です。これは数値データ用に使用される2つの組み込みスカラーの1つです。
浮かぶ (Ukabu)
Floatは、符号付きの倍精度小数値です。これは、小数点を含む符号(正または負)のついた数値であり、例えば1.2のような値です。これは、数値データに使用される他の組み込みスカラーです。
文字列 (mojiretsu)
文字列はUTF-8の文字のシーケンスです。文字列型は、任意のテキストデータに使用されます。これには非常に大きな数値などのデータも含まれます。ほとんどのカスタムスカラーは、文字列データのタイプであることがあります。
ブール値
ブール値は真か偽の値です。
アイディー
IDはユニークな識別子です。この値は、IDが数値であっても常に文字列としてシリアライズされます。IDのタイプは、普通は永久固有識別子(UUID)で表されます。
カスタムスカラー
これらの組み込みスカラーに加えて、scalarキーワードを使ってカスタムスカラーを定義することもできます。カスタムスカラーを使用することで、DateやTime、Urlなどの追加のサーバーレベルのバリデーションを持つタイプを作成することができます。以下に新しいDateタイプを定義する例を示します。
scalar Date
サーバーは、GraphQLScalarTypeを使用して、この新しいタイプとの相互作用を適切に処理する方法を知っています。
列挙型 (れっきょがた)
Enum型は、列挙型とも呼ばれ、可能な値の集合を表します。
他のGraphQLシリーズのチュートリアルからFantasy Game APIのテーマを使用して、ゲームキャラクターの職業と種族のための列挙型を作成することができます。 すべてのシステムが受け入れる値として、列挙型は以下のようにenumキーワードで定義されます。
"The job class of the character."
enum Job {
FIGHTER
WIZARD
}
"The species or ancestry of the character."
enum Species {
HUMAN
ELF
DWARF
}
この方法により、キャラクターの職業が「戦士」または「魔法使い」になることが保証され、誤って「パープル」やその他のランダムな文字列になる可能性はありません。もし独自のEnumを使用しなかった場合、String型を使用することで可能性があります。Enumは慣例的に全て大文字で書かれます。
列挙型は引数として受け入れる値としても使用することができます。例えば、武器が片手であるか(短剣のような)両手であるか(重い斧のような)を示すために、Handという列挙型を作成し、それを使用して装備できるかどうかを判断することができます。
enum Hand {
SINGLE
DOUBLE
}
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
hand: Hand
}
type Query {
weapons(hand: Hand = SINGLE): [Weapon]
}
Hand(手)の列挙体は、SINGLE(シングル)とDOUBLE(ダブル)の値で宣言されています。また、weapons(武器)の引数には、デフォルト値としてSINGLEが設定されています。つまり、引数が指定されない場合は、自動的にSINGLEに戻ります。
ノンヌル型
あなたは、多くの言語でプリミティブと考えられる共通のタイプであるnullやundefinedが、組み込みスカラーのリストから抜けていることに気づくかもしれません。GraphQLにはnullが存在し、値の不在を表します。
すべてのタイプはGraphQLではデフォルトでnull許容です。そのため、どのタイプでもnullが有効な応答となります。値を必須にするためには、GraphQLの非nullタイプに変換する必要があります。非nullはタイプ修飾子として定義されており、参照しているタイプを修飾するために使用されるタイプの一種です。例えば、Stringはオプション(もしくはnull許容)の文字列であり、String!は必須(もしくは非null)の文字列です。
リストタイプ
GraphQLにおけるリストタイプは、別の型修飾子です。角括弧([])で囲まれた任意の型は、リストタイプになります。リストタイプは、リスト内の各アイテムの型を定義するコレクションです。
例えば、[Int]と定義された型は、Int型のコレクションであり、[String]はString型のコレクションとなります。Non-NullとListは、[String]! のように、型を必須にし、かつリストとして定義するために一緒に使うことができます。
オブジェクトの種類
もしGraphQLのスカラータイプが、階層構造の最後の「葉」を表すのであれば、オブジェクトタイプは中間の「枝」を表し、GraphQLスキーマのほとんどはオブジェクトの一種です。
オブジェクトは、名前付きフィールド(キー)のリストと、それぞれのフィールドが解決される値の型で構成されます。オブジェクトは、typeキーワードで定義されます。少なくとも1つ以上のフィールドを定義する必要があり、フィールドはGraphQLの内視的システムとの競合を避けるために、2つのアンダースコア(__)で始まることはできません。
GraphQLのファンタジーゲームAPIの例では、ゲーム内のキャラクタータイプを表すFighterオブジェクトを作成することができます。
"A hero with direct combat ability and strength."
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
}
この例では、Fighterオブジェクト型が宣言され、4つの名前付きフィールドを持っています。
- id yields a Non-Null ID type.
- name yields a Non-Null String type.
- level yields an Int type.
- active yields a Non-Null Boolean type.
宣言の上には、この例のように二重引用符を使用してコメントを追加することもできます。「直接的な戦闘能力と強さを持つヒーロー」として、これがタイプの説明として表示されます。
この例では、各フィールドはスカラータイプに解決しますが、オブジェクトフィールドは他のオブジェクトタイプにも解決することができます。たとえば、Weaponタイプを作成し、GraphQLスキーマを設定することで、FighterのweaponフィールドはWeaponオブジェクトに解決されるようにすることができます。
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
}
"A hero with direct combat ability and strength."
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
weapon: Weapon
}
他のオブジェクトのフィールドにオブジェクトを入れ子にすることもできます。
ルート操作の種類
GraphQLスキーマのエントリーポイントとして3つの特別なオブジェクトがあります: クエリ、ミューテーション、サブスクリプションです。これらはルート操作タイプとして知られており、他のどのオブジェクトタイプとも同じルールに従います。
スキーマキーワードはGraphQLスキーマへのエントリーポイントを表します。ルートのQuery、Mutation、およびSubcriptionタイプは、ルートスキーマオブジェクトにあります。
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
クエリタイプはGraphQLスキーマにおいて必要であり、REST APIのGETと同様にリクエストを読み込みます。以下は、Fighterタイプのリストを返すルートクエリオブジェクトの例です。
type Query {
fighters: [Fighter]
}
以下の例では、突然変異はPOST、PUT、またはDELETEに相当する書き込みリクエストを表しています。次の例では、突然変異には、名前付き引数(input)を持つaddFighterフィールドがあります。
type Mutation {
addFighter(input: FighterInput): Fighter
}
最終的に、サブスクリプションはイベントストリームと対応しており、WebアプリケーションでWebsocketと組み合わせて使用されます。GraphQL Fantasy APIでは、たとえばランダムな戦闘エンカウントに使用できるかもしれません。
type Subscription {
randomBattle(enemy: Enemy): BattleResult
}
いくつかのGraphQL実装では、スキーマのエントリーポイントはしばしば抽象化されることに注意してください。
フィールド引数
GraphQLオブジェクトのフィールドは、基本的に値を返す関数であり、それらはどんな関数でも引数を受け入れることができます。フィールドの引数は、引数の名前に続く型で定義されます。引数は、非オブジェクト型であることができます。この例では、Fighterオブジェクトはidフィールドによってフィルタリングされることができます(これはNonNull ID型に解決されます)。
type Query {
fighter(id: ID!): Fighter
}
この特定の例は、データストアから単一のアイテムを取得するのに便利ですが、引数はフィルタリング、ページネーション、およびその他のより具体的なクエリにも使用することができます。
インターフェースのタイプ
オブジェクト型と同様に、抽象インターフェース型は名前付きフィールドとそれに関連する値の型のリストで構成されます。インターフェースはオブジェクトと同じルールに従い、外部からはオブジェクトの一部の実装を定義するために使用されます。
あなたのスキーマには、これまでに戦士オブジェクトがありますが、それに加えて魔法使いや癒し手など、同じフィールドを共有するオブジェクトを作成することも考えられます。その場合、インターフェースを使用して共通のフィールドを定義し、インターフェースの実装としてオブジェクトを作成できます。
以下の例では、すべてのタイプのキャラクターが持つすべてのフィールドをインターフェースキーワードを使用してBaseCharacterインターフェースを作成することができます。
"A hero on a quest."
interface BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job
}
すべてのキャラクタータイプには、id、名前、レベル、種族、職業のフィールドがあります。
今、ファイタータイプとウィザードタイプがあり、共通のフィールドを持っていますが、ファイターは武器を使用し、ウィザードは魔法を使用します。実装キーワードを使用して、それぞれをBaseCharacterの実装として明示できます。これは、作成したインターフェースのすべてのフィールドを持つ必要があることを意味します。
"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
weapon: Weapon
}
"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
spells: [Spell]
}
ファイターとウィザードは、必要なフィールドのサブセットを持っているため、BaseCharacterインターフェースの有効な実装となります。
ユニオンタイプ
オブジェクトと一緒に使用できる別の抽象型は、Union型です。unionキーワードを使って、すべてが有効な応答として使用できるオブジェクトのリストを持つ型を定義することができます。
前のセクションで作成したインターフェースを使用すると、キャラクターユニオンを作成できます。このユニオンは、キャラクターをウィザードまたはファイターとして定義します。
union Character = Wizard | Fighter
等しい文字(=)は定義を設定し、パイプ文字(|)はOR文として機能します。なお、Unionはオブジェクトまたはインターフェースで構成する必要があります。スカラータイプはUnionでは無効です。
今、キャラクターリストをクエリすると、Character Unionを使用して、すべてのウィザードとファイタータイプを返すことができます。
結論
このチュートリアルでは、GraphQLの型システムを定義する多くのタイプについて学びました。最も基本的なタイプはスカラータイプであり、スキーマツリーの葉として機能する値であり、Int、Float、String、Boolean、ID、さらにはGraphQLの実装によって作成されるカスタムスカラーが含まれます。Enumは有効な定数値のリストであり、単にStringとして宣言するだけではなく、レスポンスをより制御する必要がある場合に使用できるものであり、またスキーマツリーの葉でもあります。ListとNon-Nullタイプは、タイプ修飾子またはラッピングタイプとして知られており、他のタイプをコレクションまたは必須として定義することができます。オブジェクトはスキーマツリーの枝であり、クエリ、ミューテーション、サブスクリプションのエントリーポイントを含むGraphQLスキーマのほとんどすべてがオブジェクトの一種です。InterfaceとUnionタイプは、オブジェクトの定義に役立つ抽象的なタイプです。
さらに学ぶために、Node.jsでGraphQL APIサーバーを設定する方法のチュートリアルを読んで、作業可能なGraphQLサーバー環境を持つことで、GraphQLスキーマの作成と変更の練習ができます。