GraphQLタイプシステムの理解

著者は、無料かつオープンソースの基金を寄付の対象として選び、Write for Donations プログラムの一環として寄付しました。

紹介

GraphQLは、フロントエンドとデータソースの間のコミュニケーションを促進するための現代的な解決策です。GraphQLの実装の詳細と機能は、GraphQL スキーマに詳細に記載されています。機能するGraphQLスキーマを作成するには、GraphQLタイプシステムを理解する必要があります。

この記事では、GraphQLのタイプについて学びます。5つの組み込みスカラー型、EnumsListおよびNon-Nullラップ型、オブジェクト型、およびそれらと連携する抽象インターフェースおよびユニオン型について解説します。各タイプの例を確認し、それらを使用して完全なGraphQLスキーマを構築する方法を学びます。

前提条件

このチュートリアルを最大限に活用するには、以下の理解が必要です:

スカラータイプ

GraphQLスキーマ内のすべてのデータは、最終的にさまざまなスカラータイプに解決されます。これらは原始値を表します。GraphQLの応答は木として表現でき、スカラータイプは木の端にある葉です。入れ子になった応答には多くのレベルがありますが、最後のレベルは常にスカラー(またはEnum)タイプに解決されます。GraphQLには、IntFloatStringBooleanIDの5つの組み込みスカラータイプが付属しています。

Int

Intは、32ビットの符号付き非小数の数値です。これは小数を含まない符号付き(正または負)の整数です。符号付き32ビット整数の最大値は2,147,483,647です。これは数値データに使用される2つの組み込みスカラーのうちの1つです。

Float

A Float is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.2. This is the other built-in scalar used for numerical data.

String

A String is a UTF-8 character sequence. The String type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.

Boolean

A Boolean is a true or false value.

ID

IDは一意の識別子です。この値は常に文字列としてシリアル化されます。たとえIDが数値であってもです。IDタイプは一般的にUniversally Unique Identifier (UUID)で表されることがあります。

カスタムスカラー

これらの組み込みスカラーに加えて、scalar キーワードを使用してカスタムスカラーを定義することができます。カスタムスカラーを使用して、DateTime、またはUrlなどの追加のサーバーレベルの検証が必要なタイプを作成できます。次は、新しいDateタイプを定義する例です:

scalar Date

サーバーは、この新しいタイプとの対話を処理する方法をGraphQLScalarTypeを使用して知っています。

Enumタイプ

Enumタイプ、またはEnumeratorタイプとも呼ばれるものは、可能な値のセットを記述します。

他の「GraphQLでデータを管理する方法」シリーズのチュートリアルからのFantasy Game APIテーマを使用して、ゲームキャラクターのJobおよびSpeciesに対してシステムが受け入れるすべての値に対するenumを作成するかもしれません。Enumは、次のようにenumキーワードで定義されます:

"The job class of the character."
enum Job {
  FIGHTER
  WIZARD
}

"The species or ancestry of the character."
enum Species {
  HUMAN
  ELF
  DWARF
}

この方法では、キャラクターのJobFIGHTERまたはWIZARDであり、誤って"purple"などのランダムな文字列になることはありません。これは、Stringタイプを使用せずにカスタムEnumを作成することによって可能です。Enumは慣例としてすべて大文字で書かれます。

また、Enumは引数で受け入れられる値としても使用できます。たとえば、Hand enumを作成して、武器が片手持ち(短剣のような)か両手持ち(重い斧のような)かを示し、それを使用して装備できる武器が1本か2本かを判断することができます:

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 enumSINGLEDOUBLEを値として宣言されており、weaponsフィールドの引数にはデフォルト値としてSINGLEが設定されています。つまり、引数が渡されない場合はSINGLEにフォールバックします。

NotNullタイプ

多くの言語でプリミティブと見なされるnullまたはundefinedが、組み込みのスカラーのリストから欠落していることに気付くかもしれません。 NullはGraphQLに存在し、値の不在を表します。

すべてのタイプは、デフォルトでGraphQLではnull可能です。したがって、nullは任意の型の有効な応答です。値を必須にするには、GraphQLのNon-Nullタイプに変換する必要があります。Non-Nullは、参照している型を変更するために使用される型修飾子で定義されています。例えば、Stringはオプション(またはnullable)の文字列であり、String!は必須(またはNon-Null)の文字列です。

List Type

A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]) becomes a List type, which is a collection that defines the type of each item in a list.

たとえば、[Int]と定義されたタイプはIntタイプのコレクションになり、[String]Stringタイプのコレクションになります。Non-NullとListは一緒に使用して、型を必須にしてリストとして定義することができます。例えば、[String]!のように。

Object Type

もしGraphQLスカラータイプが階層的なGraphQL応答の末端にある”葉”を表すのであれば、Objectタイプは中間の”枝”を表し、GraphQLスキーマのほとんどがObjectの一種です。

オブジェクトは、名前付きのフィールド(キー)のリストと、各フィールドが解決される値の型で構成されています。オブジェクトは、typeキーワードで定義されます。少なくとも1つ以上のフィールドを定義する必要があり、フィールドはGraphQLの内観システムとの競合を避けるために、二重アンダースコア(__)で始まることはできません。

GraphQLファンタジーゲームAPIの例では、Fighterオブジェクトを作成して、ゲーム内のキャラクタータイプを表すことができます:

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
}

この例では、Fighterオブジェクトタイプが宣言され、4つの名前付きフィールドがあります:

  • idはNonNullのID型を返します。
  • nameはNonNullのString型を返します。
  • levelInt型を返します。
  • activeはNonNullのBoolean型を返します。

宣言の上には、このようにダブルクォーテーションを使用してコメントを追加することもできます:"直接の戦闘能力と強さを持つ英雄。"。これはタイプの説明として表示されます。

この例では、各フィールドがスカラータイプに解決されますが、オブジェクトフィールドは他のオブジェクトタイプに解決されることもあります。たとえば、Weaponタイプを作成し、GraphQLスキーマを設定して、Fighterweaponフィールドが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つの特別なオブジェクトがあります:クエリ、ミューテーション、サブスクリプションです。これらはルート操作の種類として知られ、他のどのオブジェクト型と同じルールに従います。

schemaキーワードはGraphQLスキーマへの入り口を表します。ルートのクエリ、ミューテーション、およびサブスクリプションの型はルートschemaオブジェクト上にあります:

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

クエリ型はGraphQLスキーマで必須であり、読み取りリクエストを表します。これはREST APIのGETと類似しています。次は、Fighter型のリストを返すルートQueryオブジェクトの例です:

type Query {
  fighters: [Fighter]
}

ミューテーションは書き込みリクエストを表し、これはREST APIのPOSTPUT、またはDELETEに類似しています。次の例では、Mutationに名前付き引数(input)を持つaddFighterフィールドがあります:

type Mutation {
  addFighter(input: FighterInput): Fighter
}

最後に、サブスクリプションはイベントストリームに対応し、Webアプリ内でWebsocketと共に使用されます。GraphQL Fantasy APIでは、ランダムな戦闘エンカウンターに使用できるかもしれません:

type Subscription {
  randomBattle(enemy: Enemy): BattleResult
}

schemaの入り口は、一部のGraphQL実装では頻繁に抽象化されます。

フィールド引数

GraphQLオブジェクトのフィールドは基本的に値を返す関数であり、任意の関数と同様に引数を受け入れることができます。フィールド引数は、引数の名前に続いて型が定義されます。引数はオブジェクト型以外の任意の型にすることができます。この例では、Fighterオブジェクトをidフィールドでフィルタリングできます(これはNonNull ID型に解決されます):

type Query {
  fighter(id: ID!): Fighter
}

この特定の例は、データストアから単一のアイテムを取得するのに便利ですが、引数はフィルタリング、ページネーション、およびその他のより具体的なクエリにも使用できます。

インターフェース型

オブジェクト型と同様に、抽象的なインターフェース型は、名前付きフィールドのリストとそれに関連する値の型で構成されます。インターフェースはオブジェクトの実装のサブセットを定義するために使用され、オブジェクトと全く同じルールに従います。

これまでのスキーマでは、Fighterオブジェクトを持っていますが、WizardHealer、その他共通のフィールドを多く共有するがいくつかの違いがあるオブジェクトも作成することが望ましいかもしれません。この場合、共通のフィールドを定義するためにインターフェースを使用し、そのインターフェースの実装となるオブジェクトを作成できます。

次の例では、すべてのキャラクタータイプが持つであろうフィールドを含むBaseCharacterインターフェースをinterfaceキーワードを使用して作成できます:

"A hero on a quest."
interface BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job
}

すべてのキャラクタータイプは、idnamelevelspeciesjobというフィールドを持ちます。

今、FighterタイプとWizardタイプがこれら共有フィールドを持っているが、FightersWeaponを、WizardsSpellsを使用すると想像してください。implementsキーワードを使用して、それぞれを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]
}

FighterWizardは、必要なフィールドのサブセットを持っているため、BaseCharacterインターフェースの有効な実装です。

Union Type

別のオブジェクトで使用できる抽象型は、Union型です。 union キーワードを使用して、応答として有効なオブジェクトのリストを含む型を定義できます。

前のセクションで作成したインターフェースを使用して、Character Unionを作成できます。これにより、キャラクターをWizardまたはFighterとして定義します:

union Character = Wizard | Fighter

等しい文字(=)は定義を設定し、パイプ文字(|)はORステートメントとして機能します。 Unionはオブジェクトまたはインターフェースで構成されている必要があります。スカラータイプはUnionで有効ではありません。

これでキャラクターのリストをクエリすると、Character Unionが使用され、すべてのWizardおよびFighterタイプが返される可能性があります。

結論

このチュートリアルでは、GraphQLタイプシステムを定義するさまざまなタイプについて学びました。最も基本的なタイプはスカラータイプであり、これらはスキーマツリー上の葉として機能し、IntFloatStringBooleanID、およびGraphQLの実装が作成することを決定するカスタムスカラーで構成されます。Enumは定数値の有効なリストであり、Stringとして単に宣言するよりも応答をより細かく制御する必要がある場合に使用できます。これらもスキーマツリー上の葉です。ListおよびNon-Nullタイプは、型修飾子またはラッピングタイプとして知られ、コレクションまたは必須として他のタイプを定義できます。オブジェクトはスキーマツリーの枝であり、GraphQLスキーマのほとんどすべてがquerymutationsubscriptionエントリーポイントを含むオブジェクトの一種です。InterfaceおよびUnionタイプは、オブジェクトを定義するのに役立つ抽象タイプです。

さらに学習するには、How to Set Up a GraphQL API Server in Node.jsチュートリアルを読んで、動作するGraphQLサーバー環境を作成および変更する練習をすることができます。

Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system