GraphQLとは何か?
November 4, 2022
GraphQLとは何か?その人気は?
GraphQLは、Facebookが2015年にリリースしたAPI指向のクエリ操作言語です。他のAPI設計とは異なり、GraphQLはクライアントが事前に合意されたデータ構造に基づいてクエリ文を形成し、サーバーがその文を解析して必要なものだけを返すことを可能にします。これにより、GraphQLは豊富さと柔軟性を提供しながら、冗長なデータによるパフォーマンスの低下を回避し、多くの複雑なデータオブジェクトを扱う必要があるアプリケーションにとって優れた選択肢となっています。
2018年、GraphQLは完全な仕様と安定版をリリースしました。同年、FacebookはGraphQLプロジェクトをLinux Foundation傘下のGraphQL Foundationに寄付しました。それ以来、GraphQLは多くのオープンソースプロジェクトや商業組織に採用されています。現在、市場にはいくつかの主要なクライアントサイドのGraphQL実装が存在し、サーバーサイドの実装も主要なサーバーサイドプログラミング言語で利用可能で、DやRのようなニッチな言語でも利用できます。
GraphQLの実際のシナリオと課題
GraphQLの最も有名な例は、GitHubのGraphQL APIです。
GraphQLを採用する前、GitHubはREST APIを提供し、数百万のホストされたプロジェクトによって生成された豊富なデータを公開していました。これは非常に成功し、REST APIを設計する際の模範となりました。しかし、データオブジェクトの数が増え、オブジェクト内のフィールドが大きくなるにつれて、REST APIはますます多くの欠点を露呈し始めました。サーバー側では、GitHubは各呼び出しで生成されるデータ量を削減するために、呼び出し頻度に厳しい制限を設ける必要がありました。開発者側では、単一の呼び出しで多くのデータが返されるものの、そのほとんどが無駄であるという制限に対処しなければなりませんでした。特定の情報を取得するために、開発者はしばしば複数のクエリを実行し、クエリ結果から意味のあるデータを結合して目的の内容を作成するために多くのグルーコードを書く必要がありました。その過程で、彼らは「呼び出し回数」の制約にも直面しなければなりませんでした。
そのため、GitHubはGraphQLが登場するとすぐに採用しました。GitHubはGraphQLの「大使」となり、そのアプリケーションを数千の開発者に提供しました。GraphQL APIは現在、GitHubの最優先選択肢となっています。GraphQLのサポートを最初に発表して以来、GitHubは毎年GraphQLに関するいくつかの記事を投稿しています。開発者がGraphQLに移行できるように、GitHubは特にこの目的のためにインタラクティブなクエリアプリケーションを書いています: https://docs.github.com/en/graphql/overview/explorer。開発者はこのアプリケーションを通じてGraphQLの書き方を学ぶことができます。
しかし、GraphQLは万能薬ではありません。最近、GitHubはパッケージAPIのGraphQL実装を非推奨にしました。また、多くの人々がGraphQLのいくつかの欠点について議論を始めています。GraphQLの問題の多くは、その構造がHTTP標準と大きく異なるため、GraphQLの概念の一部をHTTPパス/ヘッダーのような構造に簡単にマッピングできないことに起因しています。GraphQLを通常のHTTP APIとして扱うには、追加の開発作業が必要です。その結果、独自のGraphQL APIを管理したい開発者は、GraphQL対応のAPIゲートウェイを使用する必要があります。
APISIXがGraphQLをサポートする方法
現在、APISIXはGraphQLのいくつかのプロパティを通じて動的ルーティングをサポートしています。この機能により、特定のGraphQLリクエストのみを受け入れたり、異なるGraphQLを異なるアップストリームに転送したりすることができます。
以下のGraphQL文を例として取り上げます:
query getRepo {
owner {
name
}
repo {
created
}
}
APISIXは、ルーティングのためにGraphQLの以下の3つのプロパティを抽出します:
- graphql_operation
- graphql_name
- graphql_root_fields
上記のGraphQL文では:
graphql_operation
はquery
に対応しますgraphql_name
はgetRepo
に対応しますgraphql_root_fields
は["owner", "repo"]
に対応します
APISIXのGraphQLに対する細かいルーティング機能を実証するために、ルートを作成します:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"methods": ["POST"],
"uri": "/graphql",
"vars": [
["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:2022": 1
}
}
}'
次に、GraphQL文を含むリクエストでアクセスします:
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
owner {
name
}
repo {
created
}
}'
HTTP/1.1 200 OK
...
クエリ文が3つの条件すべてに一致したため、リクエストがアップストリームに到達したことがわかります。
逆に、一致しない文でアクセスすると、例えばownerフィールドが含まれていない場合:
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
repo {
created
}
}'
HTTP/1.1 404 Not Found
...
対応するルーティングルールに一致しません。
さらに、ownerフィールドを含まない文を別のアップストリームにルーティングするルートを作成することもできます:
curl http://127.0.0.1:9180/apisix/admin/routes/2 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"methods": ["POST"],
"uri": "/graphql",
"vars": [
["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "!", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"192.168.0.1:2022": 1
}
}
}'
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
repo {
created
}
}'
HTTP/1.1 200 OK
...
APISIXの将来のGraphQLサポートの展望
動的ルーティングに加えて、APISIXは将来、GraphQLの特定のフィールドに基づいたより多くの操作を導入する可能性があります。例えば、GitHubのGraphQL APIにはレート制限の特定の式があり、同様のルールを適用して単一のGraphQLリクエストを対応する数の「仮想呼び出し」に変換し、GraphQL固有のレート制限を実現することができます。
また、別の方法で問題を考えることもできます。アプリケーション自体は依然としてREST APIを提供し、ゲートウェイがGraphQLリクエストをRESTリクエストに変換し、RESTレスポンスをGraphQLレスポンスに変換します。この方法で提供されるGraphQL APIは、特別なプラグインを開発することなく、RBAC、レート制限、キャッシュなどの機能を実行できます。技術的な観点から、このアイデアは実装がそれほど難しくありません。結局のところ、2022年では、REST APIでさえOpenAPI仕様をスキーマとして提供する傾向があり、GraphQLスキーマとOpenAPIスキーマの間の変換と、GraphQL固有のフィールドフィルタリングを追加するだけです。(もちろん、私自身が実践していないことを認めなければなりません。詳細にいくつかの課題があるかもしれません。)
注意深い読者は、この方法で変換されたGraphQL APIは一度に1つのモデルしか操作できないことに気づくでしょう。これは明らかにGraphQLの柔軟性の要件を満たしておらず、GraphQLの衣をまとったREST APIに過ぎません。しかし、GraphQLにはschema stitchingという概念があり、実装者が複数のスキーマを結合することができます。
例として、GetEventとGetLocationという2つのAPIがあり、それぞれEvent型とLocation型を返します。
type Event {
id: string
location_id: string
}
type Location {
id: string
city: string
}
type Query {
GetEvent(id: string): Event
GetLocation(id: string): Location
}
これら2つのAPIを結合して、GetEventWithLocationという新しいAPIを作成する設定を追加できます。これは次のようになります:
type EventWithLocation {
id: string
location: Location
}
type Query {
GetEventWithLocation(id: string): EventWithLocation
}
具体的な結合の実装はゲートウェイによって行われます。上記の例では、ゲートウェイはAPIを2つに分割し、GetEventを呼び出してlocation_idを取得し、その後GetLocationを呼び出して結合されたデータを取得します。
要するに、RESTをGraphQLに変換することで、各REST APIを対応するGraphQLモデルに変換できます。そして、schema stitchingの助けを借りて、複数のモデルを1つのGraphQL APIに結合できます。このようにして、既存のREST APIの上に豊かで柔軟なGraphQL APIを構築し、REST APIの粒度で特定のプラグインを管理できます。この設計は、いくつかのAPIオーケストレーションの問題を解決します。上記の例のように、1つのAPI(Event.location_id)の出力を別のAPI(Location.id)の入力として使用します。