🤔 GraphQLはユーザーによって異なるべきか?
GraphQLはあるオリジンからデータを取得するためのインターフェースであり、GraphQL仕様がそのインターフェースの要件を定義しています。これらの要件が満たされている限り、GraphQLはその実現方法を問いません。GraphQLサーバーはJavaScriptのPromiseを使って実装されたり、Golangベースの並行アーキテクチャを使ったり、Excelファイルにマッピングされたり、その他の方法で実装されたりすることができ、これらはすべてGraphQL仕様の有効な実装となりえます。

サーバーのエンジンがどのように実装されているかは、GraphQLリクエストの正常な実行にとって重要ではありません。クライアントとサーバーのやり取りは常に同じであり、定義された構文を使ってGraphQLクエリを送信し、JSON形式で対応するレスポンスを受け取るという形で行われます。
さて、実装が重要ではないと言う場合、それはAPIのユーザーの視点から述べています。ユーザーはサーバーからデータを取得しようとしているだけであり、返されたデータがどのように生成されたかには関心がありません。
しかし、APIに携わるサーバーサイドの開発者にとっては状況が変わります。実装の詳細はまさに非常に重要です。PHPでGraphQL APIをコーディングするなら、PHPが提供する機能を活用しながら、できるだけ効率よくAPIを解決し、できるだけエレガントなアーキテクチャ設計を持てるよう最善を尽くすでしょう。

そこで、APIを保護する必要性と、APIに携わる開発者が期待する機能(再帰的なコードを実行できるなど、基盤となる言語がサポートする機能を奪われたくない)との間で、利益相反が生じる可能性があります。
この対立は、issue #929: Allow recursive references in fragments で明確になりました。このissueは、GraphQLがfragmentでの再帰を禁止すべきではないと主張しています。
GraphQLワーキンググループの過去のミートアップで、このissueを提起した開発者であるRomanは、仕様が課す制限への反対意見を次のように表明しました。
私はサーバーサイドの開発者であり、仕様がサーバーサイドの実行についてあまりにも多く語っている一方で、クライアントが何を届けてほしいのかに集中すべきだと感じています——どのようにではなく
fragmentでの再帰を禁止するルールは、公開APIのセキュリティを維持するという前提で正当化されてきました。結局のところ、GraphQLはFacebookが公開向けアプリケーションにデータを提供するために作成したものであり、ユーザーがAPIの設計上の欠陥を悪用してサービスをダウンさせることがあってはなりません。
GraphQLの作者であるLee Byronは、3つの主な懸念を表明しました。
無限再帰;制限は仕様だけではない——いつどのように停止すべきか
データの検証;同じ値を複数回返す場合、それをデータ内でどのように表現するか。理想的には循環していることを検出してすぐに停止したいが、一部のサーバーはこれを検出できず、問題を検出して停止するまでに何度もループする可能性がある
これを持たないことのコストは何か;これらの問題を正当化するか?それはしない;クエリで深さのレベル数を指定することは常に可能だ——それは事実上、GraphQLでこれを処理した場合に行うことの脱糖化バージョンである
それぞれの立場から見ると、RomanもLeeも正しいと言えます。Lee Byronは公開GraphQL APIのセキュリティを心配しています。再帰的なfragmentを避けることは、悪意ある行為者がクエリで終わりのない循環ループを実行してシステムをダウンさせることを防ぎ、チームが意図せずシステムを停止させるクエリを公開してしまう「セルフDDoS」の可能性をなくすために正当化されます。
しかしRomanは、GraphQL APIを作成するための自分自身の能力に課される制限を懸念しています。RomanがAPIの唯一の利用者である(つまりユーザーに公開されていないプライベートAPIである)場合、あるいはサーバーが循環サイクルを検出して停止する能力を持っている場合、GraphQLの制限は有害であり正当化できないと考えています。
議論の核心にあるのは、再帰的なfragmentを許可すべきかどうかではなく、より根本的な問題です。GraphQLのターゲットは誰か?単一のグループでない場合、単一のAPI仕様がすべての異なるステークホルダーの要件を満たせるのか?そして、対立を防げない場合、少なくとも何らかの形で緩和できるのか?
これらの疑問を探ってみましょう。
GraphQLのターゲットは誰か?
GraphQLはさまざまな種類のステークホルダーによって使用されており、その中には以下が含まれます。
1. APIユーザー: 何らかの理由でGraphQLエンドポイントからデータを利用する人々。例えば、GitHubの公開GraphQL APIのAPIユーザーになって、GitHubリポジトリに関するデータを取得することができます。
2. クライアントサイド開発者: GraphQLエンドポイントを動力源とするクライアントサイドアプリケーションを作成する人々。例えば、Gatsbyでサイトを構築する開発者はGraphQLを使ってサイトのコンテンツを取得しています。
3. バックエンド開発者: GraphQL APIのリゾルバーを作成する人々。
さらに、GraphQL APIはパブリックまたはプライベートであることに注意する必要があります。
パブリックAPI: GraphQLエンドポイントに誰でもアクセスできるため、悪意ある行為者による攻撃を避けるためのセキュリティ対策を講じる必要があります。
プライベートAPI: 意図された行為者のみがAPIにアクセスできるため、固有のセキュリティリスクはなく、セルフDDoSも適切なコーディング慣行によって簡単に回避できます。
単一のAPI仕様がすべてのステークホルダーの要件を満たせるか?
Romanが提起したissueはこのように解釈できます。「私のGraphQL APIがプライベートで、自分が何をしているかを正確に知っている(コードが期待どおりに動作し、実行が停止しないという100%の確信がある)なら、なぜfragmentで再帰を使えないのか?」

この状況の例は、静的サイトを構築するためにGraphQLを搭載したフレームワーク(Gatsby、Next.js、RedwoodJSなど)を使用する場合に発生します。GraphQL APIはしばしばプライベートであり、うっかりアプリケーションをDDoSして悪影響を受けることはありません(最悪でも、開発環境またはステージング環境で静的サイトを構築する際にクラッシュするだけです)。
上記の設定を使用する開発者は、GraphQL仕様が自分たちの設定にとっては何の悪影響もない有益な機能を使うことを禁じているのはなぜかと疑問に思うのも当然です。
結論として、再帰的なfragmentを禁止することで、GraphQL仕様はGraphQLのすべての潜在的な用途の中から選択された部分にのみ適用されるセキュリティ対策を安全策として課しており、すべての用途に適用されているわけではありません。
GraphQL仕様はすべてのステークホルダーをより満足させることができるか?
異なるステークホルダーが異なる要件を持っている場合、GraphQL仕様はどのようにしてすべてを満足させることができるでしょうか?(仕様をフォークして特定のターゲット向けにカスタマイズされたバージョンを作ることは避けたいという考えです。)
2つのアイデアを探ってみましょう。最初のアイデアは仕様への貢献プロセスを経る必要がありますが、2番目はそうではありません。
GraphQL仕様レベルでのfeature-toggle
取りうる可能な方法の1つは、仕様がルールを「強制」するのではなく「提案」することです。この場合、fragmentでの再帰を禁止するルールは強く推奨されますが、その機能は依然として受け入れられます。
さて、この解決策は再帰的なfragmentのデフォルト状態を「必須」から「オプション」に変更することになり、2つの否定的な結果をもたらします。
- APIがデフォルトで安全でなくなる(Lee Byronが避けたいシナリオ)
- 禁止されていたクエリが許可されるようになるため、破壊的変更が生じる
そこで、オプションを逆転させる方が良いでしょう。fragmentでの再帰はデフォルトで引き続き禁止されますが、この動作を無効にするfeature-flagを切り替える可能性を与えます。機能は明示的に無効化する必要があるため、何をしているかを理解している管理者のみが行います。
この機能は特定の設定下で最も価値があるため、GraphQLサーバーやフレームワークは設定を提供するかどうか・どのように・いつ提供するかを決定できます。例えば、Gatsbyは静的サイトを作成する際にUIを通じてオプションを目立つ形で表示し、それ以外の場合は非表示にすることができます。
一般的なアイデアは、GraphQL仕様が「有効だがオプションの機能」をサポートすることです。これらは設定によって有効化・無効化でき、そのデフォルト状態は仕様内ですでに持っている状態です。
再帰的なfragmentの禁止はその1つになり、Map型のような他のそのような機能もあり得ます。Map型はLee Byronによって仕様には受け入れられませんでした。その理由は:
Map型とキー/バリューペアのリストには大きなトレードオフがあります。1つの問題はコレクションのページング処理です。値のリストは明確なページングルールを持てますが、順序のないキーと値のペアを持つことが多いMapははるかにページングが難しいです。
もう1つの問題は使用方法です。ほとんどの場合、MapはAPIの中で値の1つのフィールドがインデックス化されているところで使われますが、私の意見ではインデックス化はストレージの問題でありクライアントキャッシングの問題であって、トランスポートの問題ではないため、これはAPIのアンチパターンです。このアンチパターンが懸念されます。APIでのMapの良い使い方もありますが、一般的な使用がこれらのアンチパターンのためになることを恐れているので、慎重に進めることを提案します。
Lee Byronはその機能がアンチパターンとして使われることへの懸念を表明しました。しかし、良い使い方があることも認めました。そこで、issueがコミュニティから多くの支持を集めた(150件以上の👍)ことを考えると、開発者にMap型をスキーマに追加することを明示的に有効にするオプションを与え、その結果に対処してもらうことができます。
GraphQLサーバーによるfeature-toggle
上記の提案が、GraphQL仕様にとってリスクが高すぎるとして支持を集めない場合、代替案はGraphQLサーバーレベルで実装することです。GraphQLサーバーはfragmentでの再帰を無効にするカスタム機能を提供できます。
アイデアを一般化すると、GraphQLサーバーは仕様の特定の機能を無効にし、仕様にない機能を有効にすることを提供できます。この動作が驚きをもたらさないようにするため、サーバーはデフォルト状態が仕様で要求されるものであることを確認し、APIの管理者は機能を切り替えることの結果を十分に認識している必要があります。(これはGato GraphQLが「innovative features」のために採用している戦略です。)
まとめ
GraphQLがますます普及するにつれて、新しい機能をサポートする新しいフレームワークがそれをスタックの一部にし、新しいステークホルダー(そしてその新しいタイプ)が関与するようになりました。そのため、もともとFacebookがアプリケーションがサーバーからデータを取得する方法を定義するために作成した仕様は、ますます多くのユースケースに対応する必要があります。
再帰的なfragmentの場合のように、あるステークホルダーのグループが他のステークホルダーにとって逆効果、あるいは有害な機能を必要とする場合、対立が生じることは避けられません。状況を改善し、不満を持つステークホルダーがGraphQLに失望しないようにするために何ができるでしょうか?
私は、仕様が機能を「無効化」する機会を提供し、何をしているかを理解している管理者が自分自身の要件を満たすためにいくつかの制限を取り除けるようにすることを主張しました。ただし、私自身はこの解決策に同意しませんが、この議論を行う必要があるため、それでも世に出します。このアイデアは物議を醸すものであるため、より良い代替案は、GraphQLサーバーがカスタム機能を通じてこの動作を提供することです。それらは明示的に有効化される必要があります。