GraphQLをもっと知りたい!

前回はGraphQLの基礎について書いたけど、今回はより深くGraphQLについて学んでいこう。参考サイトは引き続きHow To GraphQLだ。

www.howtographql.com 

便利な定義機能

フラグメンツ

フラグメンツは既存の定義を使いまわしながら、必要なフィールドだけを取ってくるようにすることができる。

例えば以下のような定義を始めに行う。

このUserのモデルから住所の情報だけをとってきたい時のために以下のようなフラグメンツを使うと便利だ。

このようにフラグメンツを定義してから、以下のクエリをクライアントから発行するとフラグメンツで記述したフィールドのデータだけを取得することができる。

 名前付きクエリ

GraphQLの強みの一つが一気に複数のクエリを発行できることだ。しかし、以下の様なクエリではエラーが発生する。

なぜならGraphQLがレンスポンスのデータを作成する際、同じフィールド名なのに引数が違うためエラーが発生するらしい。これを回避したい場合は名前付きのクエリを発行する。

おわかりいただけただろうか。このようにして、Userオブジェクトに名前をつけると、それぞれのリクエストが区別され無事に処理される。レスポンスのデータは以下のようになる。

SDLについて詳しく知ろう

 SDLには二つのタイプのデータが存在する。スカラーとオブジェクトだ。

スカラーとオブジェクト

スカラーは実際のデータの集まりのことで、GraphQLにはString, Int, Float, Boolean, IDがある。オブジェクトは、スカラーを持つフィールドの親要素だ。これまで見てきた中で言うとUserやPostがそれに当たる。

列挙型

また、データを列挙したい場合はenum(Enumerationsの略, 意味は列挙)を使って定義することができる。一週間を例に定義するとこんな感じになる。

インターフェイス

Interfaceはtypeを抽象的に表現することができる。Interfaceが適応されたtypeでは必ずInterfaceで定義されたフィールドを含まなければならない。言葉だとややこしいので実際にコードをどうぞ。

まずユーザ登録に必要な情報をUserのInterfaceで指定し、有料会員のPremiumUserのtypeではInterfaceを踏襲しながら、新たに支払い方法のフィールドが追加されています。

ユニオンタイプ

これは複数のtypeを人まとまりにして新たなtypeを作る機能です。

先に定義したAdultとChildを合わせた(Unionした)Personというユニオンタイプを定義している。

このようにクエリを発行すると、ChildとAdultの中から欲しいデータのフィールドを指定して取得することもできる。 

その他の便利な機能

使えるTypeをクライアントから調べる

さて、サーバ側でGraphQLの定義をしてきた人たちはその構造を理解していますが、クライントサイドの人たちはどのTypeが利用可能なのか理解できていません。利用可能なTypeを確認するクエリが__scheamです。

 このクエリを投げると、利用可能なTypeの名前がずらっとレスポンスで返ってくる。

統合開発環境

GraphQL Playgroundというエディタがある。統合開発環境だ。便利らしいのでぜひとも使ってみたい。自動補完機能や、スキーマを図化してくれたり、バリデーションが効いたりととにかくGraphQLを使って開発するときに欲しい機能がわんさかある。

github.com

 セキュリティ

GraphQLはクエリを発行してサーバに問い合わせるという仕組みを持つ。もし、悪意のあるユーザがこれを悪用した場合、サーバがダウンするリスクがあるのは事実だ。このリスクをなるべく低減させるため以下のような対策が有効な手段としてあると思う。

タイムアウト

もっともシンプルでよくある対策。たとえば全てのクエリは5秒以内で処理すると設定する。5秒以上かかる処理はタイムアウトエラーで中断される。ただし、もし悪意のあるクエリが5秒以内に処理が終わった場合は意味がない。また、タイムアウトを何秒以内にするかの見極めは難しい。早すぎるとほかのユーザのユーザビリティを低下させる危険性がある。

最大のクエリの深さを設定

GraphQLはこれまでみてきた通り、ネストしたクエリを発行することができる。悪意のあるユーザがやたらめったらにネストしまくったクエリを送ってきた場合、GraphQLは最大のクエリのネストの深さを設定することによってそのリクエストを拒否することができる。以下の画像が実際のネストの深さ一覧だ。クエリの深さを3に設定した場合、赤線で囲った部分は深すぎるのでこのクエリは破棄される。

Query Depth Example

この対策は実行されるまえにクエリを破棄するため、サーバへの負担がほとんど無い。しかし、これではまだすべての悪意のあるクエリを対策できるわけではない。

クエリの複雑性

クエリの複雑性というと、わかりにくいですね。これはGraphQL独自の考え方なのでコードを使って説明します。

クエリのフィールドにはそれぞれ複雑性という値が設定されており、デフォルトは1です。このクエリの場合、複雑性が1のフィールドが3つのため複雑性3のクエリといえます。もし、最大のクエリの複雑性が2に設定されていた場合、このクエリは破棄されます。

デフォルトの複雑性は1と書きましたが、ほかにどんなパターンがあるのか次のコードで示します。

postsフィールドの複雑性がほかのフィールドより高いです。これは、postsフィールドが引数を持っているためで、このように処理内容が複雑になるにつれクエリ全体の複雑性の値が上がります。

これによってクエリの深さより、より多くのケースをカバーできます。また、この方法もクエリが実行される前に破棄できます。

ロットリング

一定の時間の中で、サーバで処理する時間を制限するスロットリング。たとえば一時間に5分と設定した場合、各ユーザは5分の間で1000msだけサーバにリクエストすることができ、1000msを使い切ったら5分すぎるまでリクエストを破棄するというもの。これは非常に良い対策です。しかし、実際に一つのクエリでどれくらいの時間を要するかは想定することが難しい場合もあります。そんな時は、時間ではなく、クエリの複雑性をベースにスロットリングしてみましょう。

クエリの複雑性を使ったスロットリング

たとえば、一秒ごとにリクエストできる最大のクエリの複雑性は9と設定したとすると、短時間で超大量のリクエストが来たときも対応できます。また、正規のクエリの複雑性の値を理解していればほかのユーザも快適に利用できる値をスロットリングに設定できます。

そして、実はGithubが公開しているAPIはこの手法を取り入れています。GithubAPI制限について詳しい内容は以下のリンクからどうぞ。

developer.github.com

まとめ 

というわけで、How To GraphQLをここまで見てきましたが、とても素晴らしい充実した内容だったと思います。このような素敵な解説が出てくるGraphQLコミュニティも最高だと感じました。日本でも大いに広がってほしいGraphQLの輪。

参考サイト:

Schemas and Types | GraphQL