GraphQL
https://gyazo.com/22cf18a8cf7107085898c8562a7269e6
概要
REST API と比較して、API の速度や柔軟性、開発のしやすさを重要視して設計されている これにより、特定の言語やフレームワークに依存しない
一方、学習コストは別途発生する
独自のクエリ言語でリクエスト
特徴
REST API の問題が解消されている
単一の API エンドポイント
独自のクエリ言語を利用してリクエストを行う
クライアントが必要してデータを返す
モバイル端末の普及によりニーズが増えた
限られた帯域幅で効率的にデータを取得する必要性が増した
Web と iOS、Android 間で異なる UI の要求に答えられるようにするため
UI コンポーネントによる自律分散したデータ取得が可能
REST API で UI コンポーネントを構築する場合、大きく以下の 2 つの選択肢が取れる 1. ページコンポーネントで必要なデータを取得する
親コンポーネントが子孫コンポーネントの必要なデータまで管理する必要があり、凝集度 が低い(低凝集) 2. 各コンポーネントが対応するデータを取得する
各コンポーネントでリクエストが発生するため、パフォーマンスに懸念がある
Fragment を用いて、各 UI コンポーネントに必要なデータを宣言することで自律分散したデータ管理が可能に
各コンポーネントで宣言された Framgment は最終的に 1 つのクエリにまとめられてリクエストされるため、パフォーマンスの懸念も解消される
強力な型システムとツールチェーン
ビルドインの強力な型システムを提供
code:graphql
task Task {
id: Int!
name: String!
dueDate: String!
status: String!
description: String
}
加えて、以下のような強制力がある
ただし、Code first というアプローチの場合は不要 radish-miyazaki.icon 2. リクエストやレスポンスに型違反がある場合は、ランタイムエラーとなる
この強制力により、以下のメリットが享受できる
型違反をランタイムエラーとするため、ドキュメントと実装の乖離が起きづらい
イントロスペクションという機能も提供する
潤沢なサードパーティ製のコード生成ライブラリも存在する
API を進化させやすい
deprecated ディレクティブ
非推奨を示すためのアノテーションが組み込みで用意されている
code:gql
type Post {
publishedAtUnixTime: Int! @deprecated(reason: "代わりに publishedAt を使用してください")
publishedAt: String!
}
ユースケースがフィールドレベルで確認できる
REST API : クライアントがレスポンスのどのフィールドを実際に使用しているのかは、実装を見るまで分からない 問題点
クライアントのリクエストに応じて異なるので、サーバ側の負荷予測が難しい
クライアントが自由にクエリを作成できるため、大量のフィールドや深いネストのクエリが発行されると、サーバに負荷がかる
負荷への対策が必須
クエリの負荷監視
(外部向け)高負荷なクエリを未然に防ぐために、クエリのフィールド数やノード数、深さに制限を加える
(内部向け)事前に登録されたクエリのみを受け付けるようにする
キャッシュが複雑になりやすい
REST API の場合、エンドポイントごとにキャッシュすればよいので管理が楽
REST API との比較
table:_
REST API GraphQL
エンドポイント リソースごと 1 つ
データ操作の種類 GET / POST / PUT / DELTETE Query / Mutation
データ取得の柔軟性 低い 高い
型 弱い 強い
学習難易度 低い 高い
Fragment
クエリを再利用可能にする機能
重複した箇所を Fragment として切り出すことで、DRY なクエリを作成可能 定義には fragment キーワードを用いて、任意の名前を付ける必要がある
また、どの型に属する Fragment なのかを示すために、on {型名} を記述する必要がある
code:gql
fragment PostItem on Post {
title
author {
name
posts {
tags {
name
}
}
}
}
利用側
code:gql
query DryQuery {
post1: post(id: "1") {
...PostItem
},
post2: post(id: "1") {
...PostItem
},
}
変数
各クエリは、変数を用いて外から値を受け取るようにすることが可能
変数
$ から始まる任意の変数名
変数毎に型を指定する必要がある
変数として利用する値は、クエリとは別に作成する必要がある
ページ下部の Variables から JSON 形式で入力する https://scrapbox.io/files/66f12afa45646c001db5b425.png
ディレクティブ
@ で始まる修飾子
ビルドインのものもあれば、独自で定義することも可能
code:gql
directive @constraint(maxLength: Int) on FIELD_DEFINITION
@deprecated: フィールドが廃止予定であることを知らせるビルドインディレクティブ
e.g.
code:gql
type Post {
title: String! @deprecated
body: String! @deprecated(reason: "Use content instead.")
content: String! @constraint(maxLength: 1000)
}
@include: if に渡された値が true の場合のみ結果を取得する
@skip: if に渡された値が true の場合のみ結果を取得しない
これらは、計算負荷が非常に高いフィールドをある条件でしか利用しない場合や、ユーザが特定の権限を持つ場合のみ取得したい状況で有用
warning.icon ただし、クエリの 認知的負荷 を上げたり、最終的な実行結果が分かりづらくなるため、困った時に使う手段として捉えておくのが良い e.g.
code:gql
query IncludeQuery($showRanking: Boolean!) {
post {
title
ranking @include(if: $showRanking)
}
}
参考