AWSのAppSyncを試しに使ってみたところAPIがGraphQLでした。使い方を知らなかったので調べたメモです。
そのまま抜粋したり、要約したり、いろいろです。。
目次
- GraphQLとは
- GraphQLの特徴
- メリット、デメリット
- クエリ言語(3種類)
- スキーマ言語
- リゾルバ
- GraphQLサーバー
- クエリ、スキーマ定義の記述方法(文法)
- RESTのCRUDをGraphQLで定義する例
- subscriptionの仕様
- subscriptionの定義
- subscriptionのテスト
- チュートリアル
- 参考リソース
GraphQLとは
GraphQLは2015年にFacebookによって公開されたRESTとは異なるアプローチの です。
Facebook,GitHub,Netflixなど,多くの企業が採用。
公式サイト:https://graphql.org/
GraphQLの特徴
エンドポイントが1つだけで、クエリで詳しく指定する形。
RESTを置き換えるものではなく、使い分けるとか聞いたなあ。。はっきりとはまだわかっていませんが…
FAQ | GraphQL より。
GraphQL が REST とどのように比較されるかについての独自の見解については、 How To GraphQL を 参照してください。
GraphQL vs REST - A comparison
メリット、デメリット
メリット、利点
REST APIだと欲しいデータを全て取得するのに何度もリクエストが必要だったり、1つのリクエストに不要な情報が入っていても全て取得するしかなかったりという問題がありました。
GraphQLではその点が改善されており、必要とする項目だけを指定して取得することができるため、効率がいいね〜。
デメリット
以前は使ったことがある人が少なく知見が少なかったが、2023年現在は結構普及しているようなので、これはなくなったのかな。
初めて使うときは学習コストがかかる。
クエリ言語(3種類)
query
データを取得する。
GET
mutation
データを更新する。
POST,PUT,DELETEなど
subscription
イベントの通知。
Websocketを使ったリアルタイム通信
スキーマ言語
APIの仕様を定義するためのもの。(仕様の定義のみでデータ操作はここではしていない)
GraphQLでは、クエリ言語で記述されたリクエストについて、
スキーマ言語で定義したスキーマに従ってレスポンスを生成する。
リゾルバ
スキーマ定義ではあくまでも定義のみを行なっている。データ操作を行うのはリゾルバという関数(メソッド)。
GraphQLサーバー
GraphQLをAPIとして使うときには、GraphQLサーバーが必要。
GraphQLサーバーがデータソース(DBなど)と通信する。
クエリ、スキーマ定義の記述方法(文法)
Queries and Mutations | GraphQL より。本家が英語なので自動翻訳した結果をメモしています。
記号の抜粋
#:で始めるとコメント行
$:変数
!:非ヌルの指定。(必須を表す)
String!や[Episode]!、[String!]など。
null以外の値を返すことを期待していることを意味します。
[Episode]!はリスト自体がnullは不可。リストの各要素はnull許可。
[String!]はリスト自体のnullは許可。リストの各要素はnullは不可。
nullでないことが保証されると、クライアント側でヌルチェックやアンデファインのチェックが不要になるので便利
[ ]:リストを表す
@:ディレクティブ
標準では@include(if: Boolean)、@skip(if: Boolean) の2つがある。
...:フラグメント
... on:インラインフラグメント
__:メタフィールド
(フォントの都合で紛らわしいですがアンダーバー2つ)
フィールド(Fields)
要求しているフィールドのみを取得できる。
フィールドは増やしたり減らしたりできる。
フィールドはオブジェクトを参照することもできます。
オブジェクトは
オブジェクト名 {
フィールド名
}
の形。
コメント
「#」で始めるとコメント行になる。
例)
# クエリにはコメントを付けることができます!
引数(Arguments)
REST のようなシステムでは、引数の 1 つのセット (クエリ パラメータと URL セグメント) のみをリクエストで渡すことができます。
しかし、GraphQL では、すべてのフィールドとネストされたオブジェクトが独自の引数セットを取得できるため、GraphQL は複数の API フェッチを完全に置き換えることができます。
引数をスカラー フィールドに渡して、すべてのクライアントで個別に行うのではなく、サーバーで 1 回データ変換を実装することもできます。
(わからなかった。)(スカラー型にはデフォルトではInt、文字列、真偽値などがある)
引数にはさまざまな種類を指定できる
画像では文字列とEnumeration 型を使用した例です。
GraphQL にはデフォルトの型セットが付属しています。
独自のカスタム型もOK
GraphQL 型システムの詳細についてはこちら:Schemas and Types | GraphQL
エイリアス(Aliases)
クエリのフィールド名と、結果のフィールド名は同じ名前じゃないとダメだけど、
エイリアスを使うと結果の名前を変更することができる。(名前が競合する場合など)
フラグメント(Fragments)
「...」につづけて名称
再利用可能なユニット
同じ構造が繰り返されるときに使うといいらしい。(複数のユーザーの情報をリスト表示するとか?)同じ構造のクエリを繰り返し書くと間違うかもしれないし、便利そう。
フラグメントの概念は、複雑なアプリケーション データ要件を小さなチャンクに分割するためによく使用されます。特に、多数の UI コンポーネントをさまざまなフラグメントと組み合わせて 1 つの初期データ フェッチを行う必要がある場合に使用されます。
変数も使用できます。
フラグメントは、クエリまたはミューテーションで宣言された変数にアクセスできます。(最初に「$」を付けると変数)
オペレーション名(Operation name)
queryキーワードとクエリ名は省略できるが、実際にはコードのあいまいさを軽減できるので使用するほうがいいとのこと。
次の例では、
操作タイプとしてキーワード query を、
操作名として HeroNameAndFriends を含めています。
操作タイプ
query、Mutation、subscriptionのいずれかとなります。(操作の種類が3つ)
クエリの短縮構文を使用していない限り、操作の種類は必須です。
この場合、操作の名前または変数定義を指定できません。(操作名は変数を含められないという意味?)
操作名
操作の名前(を自分でつける)。
(省略もできるらしいが)デバッグ、ログに便利なため使うのをおすすめ。
複数操作のドキュメントでのみ必要ですが(?意味がわからなかった)、デバッグやサーバー側のログに非常に役立つため、使用することをお勧めします。
何か問題が発生した場合 (ネットワーク ログまたは GraphQL サーバーのログにエラーが表示されます)、内容を解読するよりも、コードベース内のクエリを名前で識別する方が簡単です。
これは、お気に入りのプログラミング言語の関数名のように考えてください。 たとえば、JavaScript では無名関数のみを簡単に操作できますが、関数に名前を付けると、追跡、コードのデバッグ、呼び出し時のログ記録が容易になります。 同様に、GraphQL クエリとミューテーション名は、フラグメント名とともに、サーバー側でさまざまな GraphQL リクエストを識別するための便利なデバッグ ツールになります。
変数
接頭辞として「$」を付けると変数になる。
例)$variableName
検索フィールドや選択などドロップダウンなど、動的な値を渡すときに使える。
これらの動的引数をクエリ文字列に直接渡すことはお勧めできません。(クライアント側で値を組み込んたGraphQLの形式に書き直す必要が出てくるため、手間)
GraphQLでは変数を別にして渡せるので活用する。
3つの手順
1. クエリの静的な値を $variableName に置き換えます
2. $variableName を、クエリで受け入れられる変数の 1 つとして宣言します。
3. 変数名を渡します: 個別のトランスポート固有 (通常は JSON) 変数辞書の値
変数の定義
上記のクエリの ($episode: Episode) のような部分です。 型付き言語の関数の引数定義と同じように機能します。 $ で始まるすべての変数がリストされ、その後にタイプ (この場合は Episode) が続きます。
C#だと「型名 変数名」の順番で書くけど、GraphQLは逆の「変数名 型名」と書くタイプなんだね~。
変数のデフォルト値
変数にデフォルト値を設定すると、変数を渡さなかったとときにデフォルト値が使用される。
すべての変数にデフォルト値が指定されている場合、変数を渡さずにクエリを呼び出すことができます。 変数が変数ディクショナリの一部として渡されると、デフォルトが上書きされます(こっちで指定した値を使うようになる、ということだろう)。
ディレクティブ(Directives)
@include、@skip
変数を使用してクエリの構造と形状を動的に変更する方法も必要になる場合があります。
たとえば、一方が他方よりも多くのフィールドを含む、要約ビューと詳細ビューを持つ UI コンポーネントを想像できます。
ディレクティブは、クエリでフィールドを追加および削除するために文字列操作を行う必要がある状況から抜け出すのに役立ちます。
例)変数$withFriends に falseが入っている例。
コア GraphQL 仕様には2つのディレクティブがある(標準では2つのディレクティブがある)。
・@include(if: Boolean) 引数がtrueの場合のみ、このフィールドを結果に含める。
・@skip(if:Boolean) 引数が true の場合、このフィールドをスキップします。(たぶん、含めないという意味)
仕様に準拠した GraphQL サーバー実装でサポートする必要があります。→たぶん引数を使ってサーバー側では判定による実装が必要っぽい
サーバー側で新しくディレクティブを定義することで、ディレクティブを追加することもできるらしい。(ユーザー定義のディレクティブが作成可能っぽい)
ミューテーション(Mutations)
サーバー側のデータを変更するにはmutationを使う。
HTTPメソッドのPOST,PUT,DELETE。
CRUDで言うところの、作成(Create)更新(Update)削除(Delete)。
RESTでもGraphQLでも、すべての要求がサーバーに何らかの副作用を引き起こす可能性がありますが、慣例により、GET 要求を使用してデータを変更することはお勧めしません。
書き込みの原因となる操作はミューテーションを介して明示的に送信する必要がある。
クエリと同様に、ミューテーション フィールドがオブジェクト タイプを返す場合は、ネストされたフィールドを要求できます。 これは、更新後にオブジェクトの新しい状態を取得するのに役立ちます。 簡単なミューテーションの例を見てみましょう。
createReview フィールドが、新しく作成されたレビューの星とコメント フィールドを返す方法に注意してください。 これは、1 回のリクエストでフィールドの新しい値を変更してクエリできるため(値の変更+最新の情報取得)、たとえばフィールドをインクリメントする場合など、既存のデータを変更する場合に特に便利です。
また、この例では、渡されたレビュー変数がスカラーではないことに気付くかもしれません。 これは入力オブジェクト タイプで、引数として渡すことができる特別な種類のオブジェクト タイプです。
(スカラー型にはInt、文字列、真偽値などがある)
(ミューテーションのときにスカラー型でなくオブジェクトを渡したいとき「入力タイプ」という型になるそうです。入力タイプは通常のオブジェクト タイプとまったく同じように見えますが、type の代わりにキーワード input を使いinput型として定義するようです。キーワードが違う以外同じにしか見えんのやけど。。なんでtype とinput で使い分けるんだろう?)
GraphQLサーバーを立てるときに使われることの多いライブラリ「Apollo」でのinput型の説明。
The input type - GraphQL Tutorials
引数をグループ化する特別なオブジェクト型。引数が多いときに1つのオブジェクトにして書けるので便利っぽい。引数にしか使えなくて戻り値に使えないという意味でいい?
input型の運用のコツが書いてありました。
GraphQL schema basics - Apollo GraphQL Docs
入力タイプは、複数の操作でまったく同じ情報セットが必要な場合に役立つことがありますが、慎重に再利用する必要があります。 操作は、必要な引数のセットで最終的に分岐する可能性があります。 Query と Mutation の両方のフィールドに同じ入力タイプを使用する場合は注意してください。 多くの場合、ミューテーションに必要な引数は、対応するクエリではオプションです。 操作の種類ごとに個別の入力の種類を作成したい場合があります。
ミューテーションの複数のフィールド
ミューテーションには、クエリと同様に複数のフィールドを含めることができます。 クエリとミューテーションには、名前以外に重要な違いが 1 つあります。
クエリ フィールドは並行して実行されますが、
ミューテーション フィールドは順次実行されます。
つまり、1 つのリクエストで 2 つの incrementCredits ミューテーションを送信した場合、最初のリクエストは 2 番目のリクエストが始まる前に終了することが保証され、自分自身と競合状態にならないようにします。
インライン フラグメント(Inline Fragments)
インラインフラグメント「... on 型名」
他の多くの型システムと同様に、GraphQL スキーマにはインターフェイスとユニオン型を定義する機能が含まれています。 詳しくはスキーマ ガイドを参照。
インターフェイスまたはユニオン型を返すフィールドをクエリする場合は、インライン フラグメントを使用して、基礎となる具象型のデータにアクセスする必要があります。 例で見るのが最も簡単です:
このクエリでは、hero フィールドはタイプ Character(インターフェイス) を返します。これは、episode 引数に応じて、Human または Droid のいずれかになります。 直接選択では、名前など、Character インターフェイスに存在するフィールドのみを要求できます。
具象型のフィールドを要求するには、型条件でインライン フラグメントを使用する必要があります。 最初のフラグメントは「... on Droid」とラベル付けされているため、primaryFunction フィールドは、hero から返された Character が Droid タイプの場合にのみ実行されます。 Human タイプの高さフィールドについても同様です。
名前付きフラグメントには常にタイプが関連付けられているため、名前付きフラグメントも同じ方法で使用できます。
メタ フィールド(Meta fields)
__typename (メタ フィールド) で型名を取得できる。
GraphQL サービスから返される型がわからない場合があるため、クライアントでそのデータを処理する方法を決定する何らかの方法が必要です。 GraphQL を使用すると、クエリの任意の時点で __typename (メタ フィールド) を要求して、その時点のオブジェクト タイプの名前を取得できます。
上記のクエリでは、search は 3 つのオプションのいずれかであるユニオン型を返します。 __typename フィールドがないと、クライアントからさまざまなタイプを区別することは不可能です。
GraphQL サービスにはいくつかのメタフィールドがあり、残りはイントロスペクションシステムを公開するために使用されます。(他には__Schema, __Typeなど)
イントロスペクション(Introspection)
GraphQL スキーマがサポートするクエリに関する情報を、GraphQL スキーマに問い合わせると便利なことがよくあります。 GraphQL では、イントロスペクション システムを使用してこれを行うことができます。
詳しくは Introspection | GraphQL
(参考)イントロスペクションについて(オブジェクトの調査) - Qiita
イントロスペクション(Introspection)
日本語で「内省」と訳されてよくわからなかったのですが
プログラミング言語においてのintrospectionは、プログラムの実行時にオブジェクトの性質や型を調べることです。
リフレクション(reflection)との違い
似たような意味としてリフレクションが存在します。
リフレクションは、プログラムの実行時にオブジェクトの値、メタデータ、性質、関数を操作することです。
RESTのCRUDをGraphQLで定義する例
CRUD(クラッド)とは、作成(Create)、読み出し(Read)、更新(Update)、削除(Delete)の頭文字から成ります。データを扱うときの基本操作の4つを指しています。
CRUDとは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
GraphQLでのCRUDは
読み出し(Read)をqueryで、
作成(Create)更新(Update)削除(Delete)をmutationで行います。
例はあとで書く予定です。。
subscriptionの仕様
仕様の サブスクリプションの作成方法と実行方法 の詳細
https://spec.graphql.org/draft/#sec-Subscription
より、抜粋、要約、メモなど…
スキーマ定義はこうなる。GraphQLの仕様書 GraphQL より
イベントストリーム
ソース ストリーム
応答ストリーム
…?どれが何?;
操作がサブスクリプションの場合、結果は「応答ストリーム」と呼ばれるイベント ストリームになります。
サブスクリプション操作を実行すると、基になるソース ストリームを返された応答ストリームにマップする永続関数がサービス上に作成されます。
チャット アプリケーションの例。
チャット ルームに投稿された新しいメッセージを購読するには、クライアントは次のようなリクエストを送信します。
クライアントから、購読開始↓
クライアントがサブスクライブしている間、新しいメッセージが ID「123」のチャット ルームに投稿されるたびに、「送信者」と「テキスト」の選択が評価され、クライアントに公開されます。次に例を示します。
クライアントに返ってくるレスポンス↓
「チャット ルームに投稿された新しいメッセージ」は、チャット ルーム ID が「トピック」であり、各「公開」に送信者とテキストが含まれる「Pub-Sub」システムを使用できます。
イベントストリーム(Event Streams)
「Pub-Sub」システムは、
「トピックをサブスクライブする」ときにイベント ストリームを生成し、
そのトピックに「パブリッシュ」するたびにそのイベント ストリームでイベントが発生します。
イベント ストリームは、
無限のイベント シーケンスを生成する場合もあれば、
任意の時点で完了する場合もあります。
イベント ストリームは、エラーに応答して、または単にそれ以上イベントが発生しないために完了する場合があります。
オブザーバー(クライアント側)は、いつでもイベント ストリームの監視をキャンセルして停止することを決定できます。その後、オブザーバーは、そのイベント ストリームからそれ以上イベントを受信してはなりません。(なんで?新しく再講読すべきということ?)
大規模なサブスクリプションのサポート
クエリとミューテーション操作はステートレスであり、GraphQL サービス インスタンスのクローン作成によるスケーリングが可能です。
対照的に、サブスクリプションはステートフルであり、サブスクリプションの存続期間にわたって GraphQL ドキュメント、変数、およびその他のコンテキストを維持する必要があります。
サービス内の 1 台のマシンの障害により状態が失われた場合のシステムの動作を考慮してください。 サブスクリプションの状態とクライアント接続を管理するための個別の専用サービスを用意することで、耐久性と可用性が向上する場合があります。
配信不可(Delivery Agnostic)
(訳の意味がわからなかった。配信についての仕様なし、とか?わからん…)
GraphQL サブスクリプションには、特定のシリアライゼーション形式やトランスポート メカニズムは必要ありません。
GraphQL は、応答ストリームの作成、そのストリームの各ペイロードのコンテンツ、およびそのストリームの終了のためのアルゴリズムを指定します。
メッセージの確認応答、バッファリング、再送信要求、またはその他のサービス品質 (QoS) の詳細については、意図的に仕様がありません。 メッセージのシリアル化、トランスポート メカニズム、およびサービスの品質の詳細は、実装するサービスによって選択される必要があります。
ソースストリーム(Source Stream)
Source Stream は一連のイベントを表し、それぞれがそのイベントに対応する GraphQL 実行をトリガーします。 フィールド値の解決と同様に、ソース ストリームを作成するロジックはアプリケーション固有です。
応答ストリーム(Response Stream)
基になるソース ストリームの各イベントは、そのイベントをルート値として使用して、サブスクリプション選択セットの実行をトリガーします。
登録解除(Unsubscribe)
登録解除 は、クライアントがサブスクリプションのペイロードを受信する必要がなくなったときに、応答ストリームをキャンセルします。 これにより、ソース ストリームもキャンセルされる場合があります。 これは、サブスクリプションで使用されている他のリソースをクリーンアップする良い機会でもあります。
subscriptionの定義
仕様より、サブスクリプション操作の定義
https://spec.graphql.org/draft/#sec-Subscription-Operation-Definitions
より、抜粋、要約、メモなど…
サブスクリプション操作には、ルート フィールドが 1 つだけ必要です。
有効な例:
subscriptionのテスト
The WebSocket API (WebSockets) - Web APIs | MDN
チュートリアル
ライブラリ「Apollo」のチュートリアル。まだ見てないけど、認定とかもあるしけっこう体系的っぽい感じがする。
参考リソース
まずは公式。自動翻訳しましたが読めなくはないですかね。サブスクリプションについては記載なし!><
仕様。サブスクリプションについてはこちらを参照。
https://spec.graphql.org/draft/#sec-Subscription
まとまってます!助かりました!↓Kindle Unlimited(たまに99円キャンペーンとかやってる…)で読めました。
サブスクリプションの記載はないですが、有名な記事だそうです
クエリの実例
Check! GitHub GraphQL で紐づいた pull requests と一緒に issues を取得するサンプルコード