REST APIはREST-in-Peace APIです。 Long Live GraphQL。

更新:この記事は現在、「GraphQLの完全な紹介」の一部です。
jscomplete.com/why-graphqlで、このコンテンツの更新版とGraphQLの詳細をお読みください。

長年REST APIを扱った後、GraphQLとGraphQLが解決しようとしている問題について初めて知ったとき、この記事の正確なタイトルをツイートすることに抵抗することはできませんでした。

もちろん、当時は、おかしなことをしようとする試みでしたが、今日はおかしな予測が実際に起こっていると信じています。

これを間違って解釈しないでください。 GraphQLをRESTを「殺す」などと非難するつもりはありません。 XMLは決して死ななかったように、RESTはおそらく死ぬことはないでしょう。 GraphQLはJSONがXMLに対して行ったことをRESTに対して行うと思う。

この記事は、実際には100%がGraphQLを支持しているわけではありません。 GraphQLの柔軟性のコストに関する非常に重要なセクションがあります。優れた柔軟性には大きなコストが伴います。

私は「常に始める理由」の大ファンなので、それをやってみましょう。

まとめ:なぜGraphQLなのか?

GraphQLが美しく解決する3つの最も重要な問題は次のとおりです。

  • ビューに必要なデータを取得するために複数のラウンドトリップを行う必要性:GraphQLを使用すると、サーバーへの1回のラウンドトリップでビューに必要なすべての初期データをいつでも取得できます。 REST APIで同じことを行うには、管理とスケーリングが難しい非構造化パラメーターと条件を導入する必要があります。
  • サーバーへのクライアントの依存関係:GraphQLを使用すると、クライアントは次の要求言語を話します。1)サーバーがデータの形状またはサイズをハードコーディングする必要がなくなり、2)クライアントをサーバーから切り離します。これは、サーバーとは別にクライアントを維持および改善できることを意味します。
  • フロントエンド開発者の悪い経験:GraphQLでは、開発者は宣言型言語を使用してユーザーインターフェイスのデータ要件を表現します。彼らは、それをどのように利用可能にするかではなく、必要なものを表現します。 UIが必要とするデータと、開発者がGraphQLでそのデータの説明を表現できる方法との間には密接な関係があります。

この記事では、GraphQLがこれらすべての問題をどのように解決するかについて詳しく説明します。

始める前に、GraphQLにまだ慣れていない方のために、簡単な定義から始めましょう。

GraphQLとは何ですか?

GraphQLは言語です。 GraphQLをソフトウェアアプリケーションに教えると、そのアプリケーションは、GraphQLを使用するバックエンドデータサービスにデータ要件を宣言的に伝えることができます。

子供が新しい言語をすばやく習得できるように-大人はそれを習得するのに苦労しますが-GraphQLを使用して新しいアプリケーションを最初から開始することは、GraphQLを成熟したアプリケーションに導入するよりもはるかに簡単です。

GraphQLを話すようにデータサービスに教えるには、ランタイムレイヤーを実装し、サービスと通信するクライアントに公開する必要があります。サーバー側のこのレイヤーは、GraphQL言語の単なる翻訳者、またはデータサービスを代表するGraphQLを話すエージェントと考えてください。 GraphQLはストレージエンジンではないため、単独でソリューションにすることはできません。これが、GraphQLだけを話すサーバーを持つことができず、代わりに翻訳ランタイムを実装する必要がある理由です。

この層は、任意の言語で記述でき、一般的なグラフベースのスキーマを定義して、それが表すデータサービスの機能を公開します。 GraphQLを使用するクライアントアプリケーションは、機能内でそのスキーマを照会できます。このアプローチにより、クライアントはサーバーから切り離され、両方のクライアントが独立して進化および拡張できるようになります。

GraphQLリクエストは、クエリ(読み取り操作)または変更(書き込み操作)のいずれかです。どちらの場合も、リクエストはGraphQLサービスが指定された形式のデータで解釈、実行、解決できる単純な文字列です。モバイルおよびWebアプリケーションで通常使用される一般的な応答形式はJSONです。

GraphQLとは何ですか? (Explain-it-like-I’m-5バージョン)

GraphQLはすべてデータ通信です。クライアントとサーバーがあり、両方とも互いに通信する必要があります。クライアントはサーバーに必要なデータを伝える必要があり、サーバーはこのクライアントのデータ要件を実際のデータで満たす必要があります。 GraphQLはこの通信の途中に入ります。

Pluralsightコースからキャプチャしたスクリーンショット— GraphQLを使用したスケーラブルなAPIの構築

クライアントがサーバーと直接通信できないのはなぜですか?確かにできます。

クライアントとサーバー間のGraphQLレイヤーを考慮する理由はいくつかあります。それらの理由の1つ、そしておそらく最も人気のある理由は、効率です。クライアントは通常、複数のリソースについてサーバーに問い合わせる必要があり、サーバーは通常、単一のリソースで応答する方法を理解しています。したがって、クライアントは最終的にサーバーへの複数の往復を行い、必要なすべてのデータを収集します。

GraphQLを使用すると、基本的にこのマルチリクエストの複雑さをサーバー側にシフトし、GraphQLレイヤーで処理することができます。クライアントはGraphQLレイヤーに単一の質問をし、クライアントが必要とするものを正確に含む単一の応答を取得します。

GraphQLレイヤーを使用すると、さらに多くの利点があります。たとえば、もう1つの大きな利点は、複数のサービスと通信することです。複数のサービスからのデータを要求する複数のクライアントがある場合、中央のGraphQLレイヤーはこの通信を簡素化および標準化できます。これはREST APIに対するポイントではありませんが、同じことを簡単に実現できますが、GraphQLランタイムは構造化され標準化された方法を提供します。

Pluralsightコースからキャプチャしたスクリーンショット— GraphQLを使用したスケーラブルなAPIの構築

クライアントが(上記のスライドの)2つの異なるデータサービスに直接アクセスする代わりに、そのクライアントにGraphQLレイヤーと通信させることができます。その後、GraphQLレイヤーは2つの異なるデータサービスとの通信を行います。これは、GraphQLが最初にクライアントを複数の言語で通信する必要性から分離し、単一のリクエストを異なる言語を使用する複数のサービスへの複数のリクエストに変換する方法です。

3つの異なる言語を話し、異なる種類の知識を持っている3人の人がいると想像してください。次に、3人すべての知識を組み合わせることによってのみ答えられる質問があると想像してください。 3つの言語すべてを話す翻訳者がいる場合、質問に対する答えをまとめる作業は簡単になります。これはまさにGraphQLランタイムが行うことです。

コンピューターはどんな質問にも答えるほどスマートではないので(少なくともまだ)、どこかのアルゴリズムに従う必要があります。これが、GraphQLランタイムでスキーマを定義する必要があり、そのスキーマがクライアントによって使用される理由です。

スキーマは基本的に、クライアントがGraphQLレイヤーに尋ねることができるすべての質問のリストを含む機能ドキュメントです。ここではノードのグラフについて説明しているため、スキーマの使用方法にはある程度の柔軟性があります。スキーマは主に、GraphQLレイヤーで回答できるものの制限を表します。

まだはっきりしない? GraphQLを、本当に簡単に呼び出しましょう。RESTAPIの代替品です。それで、あなたが今尋ねている可能性が最も高いという質問に答えさせてください。

REST APIの何が問題になっていますか?

REST APIの最大の問題は、複数のエンドポイントの性質です。これらは、クライアントがデータを取得するために複数の往復を行う必要があります。

通常、REST APIはエンドポイントのコレクションであり、各エンドポイントはリソースを表します。そのため、クライアントが複数のリソースからのデータを必要とする場合、必要なデータをまとめるためにREST APIへの複数のラウンドトリップを実行する必要があります。

REST APIには、クライアント要求言語はありません。クライアントは、サーバーが返すデータを制御できません。彼らがそうすることができる言語はありません。より正確には、クライアントが使用できる言語は非常に限られています。

たとえば、READ REST APIのエンドポイントは次のいずれかです。

  • GET / ResourceName-そのリソースからすべてのレコードのリストを取得する、または
  • GET / ResourceName / ResourceID-そのIDで識別される単一のレコードを取得します。

たとえば、クライアントは、そのリソースのレコードに対して選択するフィールドを指定できません。その情報はREST APIサービス自体にあり、REST APIサービスは、クライアントが実際に必要とするフィールドに関係なく、常にすべてのフィールドを返します。 GraphQLのこの問題に対する用語は、不要な情報の過剰取得です。クライアントとサーバーの両方にとって、ネットワークとメモリリソースの無駄です。

REST APIのもう1つの大きな問題は、バージョン管理です。複数のバージョンをサポートする必要がある場合、通常は新しいエンドポイントを意味します。これにより、これらのエンドポイントを使用および保守する際により多くの問題が発生し、サーバー上のコード重複の原因になる可能性があります。

上記のREST APIの問題は、GraphQLが解決しようとしているものに固有のものです。確かに、REST APIのすべての問題ではありません。RESTAPIとは何かを知りたくありません。私は主に、人気のあるリソースベースのHTTPエンドポイントAPIについて話しています。これらのAPIはいずれも、最終的に、通常のRESTエンドポイント+パフォーマンス上の理由で作成されたカスタムアドホックエンドポイントを含むミックスになります。これは、GraphQLがはるかに優れた代替手段を提供する場所です。

GraphQLはどのように魔法を使いますか?

GraphQLには多くの概念と設計上の決定事項がありますが、おそらく最も重要なものは次のとおりです。

  • GraphQLスキーマは、厳密に型指定されたスキーマです。 GraphQLスキーマを作成するには、タイプを持つフィールドを定義します。これらの型はプリミティブ型またはカスタム型であり、スキーマ内の他のすべてには型が必要です。このリッチタイプシステムにより、内省的なAPIを備え、クライアントとサーバーの両方に強力なツールを構築できるなどの豊富な機能が可能になります。
  • GraphQLはデータをグラフとして扱い、データは当然グラフです。データを表す必要がある場合、適切な構造はグラフです。 GraphQLランタイムを使用すると、データの自然なグラフ形状に一致するグラフAPIでデータを表現できます。
  • GraphQLには、データ要件を表現するための宣言的な性質があります。 GraphQLは、クライアントがデータニーズを表現するための宣言型言語をクライアントに提供します。この宣言的な性質により、英語でのデータ要件についての考え方に近いGraphQL言語の使用に関するメンタルモデルが作成され、GraphQL APIの操作が他の方法よりもはるかに簡単になります。

最後のコンセプトは、GraphQLがゲームチェンジャーであると個人的に信じる理由です。

これらはすべて高レベルの概念です。さらに詳細を見てみましょう。

複数ラウンドトリップの問題を解決するために、GraphQLは応答サーバーを単一のエンドポイントにします。基本的に、GraphQLはカスタムエンドポイントのアイデアを極端に取り入れ、すべてのデータの質問に答えることができるサーバー全体を単一のカスタムエンドポイントにします。

この単一のエンドポイントの概念に付随するもう1つの大きな概念は、そのカスタムの単一のエンドポイントを操作するために必要なリッチクライアント要求言語です。クライアント要求言語がなければ、単一のエンドポイントは役に立ちません。カスタムリクエストを処理し、そのカスタムリクエストのデータで応答するには、言語が必要です。

クライアントリクエスト言語を持つことは、クライアントが制御できることを意味します。彼らは必要なものを正確に尋ねることができ、サーバーは彼らが求めているものを正確に答えます。これにより、オーバーフェッチの問題が解決します。

バージョン管理に関しては、GraphQLはそれについて興味深い見解を持っています。バージョン管理はすべて同時に回避できます。基本的に、グラフがあり、ノードを追加することでグラフを柔軟に拡大できるため、古いフィールドを削除せずに新しいフィールドを追加できます。したがって、グラフに古いAPIのパスを残し、新しいバージョンとしてラベル付けせずに新しいAPIを導入できます。 APIが成長するだけです。

これは、使用しているAPIのバージョンを制御できないため、モバイルクライアントにとって特に重要です。モバイルアプリは、インストールされると、同じ古いバージョンのAPIを何年も使用し続ける可能性があります。ウェブ上では、新しいコードをプッシュするだけなので、APIのバージョンを簡単に制御できます。モバイルアプリの場合、それは非常に困難です。

まだ完全には納得していませんか? GraphQLとRESTを実際の例と1対1で比較してみてはどうでしょうか。

RESTful API vs GraphQL API —例

私たちが、スターウォーズの映画やキャラクターを表現するために、光沢のある新しいユーザーインターフェースを構築する開発者であると想像してみましょう。

最初に作成するUIのタスクは単純です。1人のスターウォーズの人物に関する情報を表示するビューです。たとえば、Darth Vader、およびこの人物が登場したすべての映画。このビューには、人物の名前、生年、惑星名、および登場したすべての映画のタイトルが表示されます。

簡単そうに思えますが、実際には、Person、Planet、Filmの3つの異なるリソースを扱っています。これらのリソース間の関係は単純であり、誰でもここでデータの形状を推測できます。人物オブジェクトは1つの惑星オブジェクトに属し、1つ以上の映画オブジェクトを持ちます。

このUIのJSONデータは次のようになります。

{
  「データ」:{
    「人」:{
      「名前」:「ダースベイダー」、
      「birthYear」:「41.9BBY」、
      「惑星」:{
        「名前」:「たといね」
      }、
      「映画」:[
        {「タイトル」:「新しい希望」}、
        {"タイトル": "帝国の逆襲"}、
        {"タイトル": "ジェダイの帰還"}、
        {「タイトル」:「シスの復even」}
      ]
    }
  }
}

データサービスからデータのこの正確な構造が得られたと仮定すると、React.jsでビューを表現する方法の1つは次のとおりです。

//コンテナコンポーネント:
 
// PersonProfileコンポーネント:
名前:{person.name}
生年:{person.birthYear}
惑星:{person.planet.name}
映画:{person.films.map(film => film.title)}

これは簡単な例であり、スターウォーズでの経験はここで少し助けになったかもしれませんが、UIとデータの関係は非常に明確です。 UIは、想像したJSONデータオブジェクトのすべての「キー」を使用しました。

RESTful APIを使用してこのデータを要求する方法を見てみましょう。

一人の人の情報が必要です。その人のIDを知っていると仮定すると、RESTful APIはその情報を次のように公開することが期待されます。

GET-/ people / {id}

このリクエストは、名前、誕生年、およびその人物に関するその他の情報を提供します。優れたRESTful APIは、この人物の惑星のIDと、この人物が登場したすべての映画のIDの配列も提供します。

このリクエストのJSONレスポンスは次のようになります。

{
  「名前」:「ダースベイダー」、
  「birthYear」:「41.9BBY」、
  「planetId」:1
  「filmIds」:[1、2、3、6]、
  ***必要のないその他の情報***
}

それから惑星の名前を読むために、私たちは尋ねます:

GET-/ planets / 1

そして、映画のタイトルを読むために、私たちは尋ねます:

GET-/ films / 1
GET-/ films / 2
GET-/ films / 3
GET-/ films / 6

サーバーから6つの応答すべてを取得したら、それらを組み合わせて、ビューに必要なデータを満たすことができます。

シンプルなUIのシンプルなデータニーズを満たすために6回の往復を行う必要があるという事実に加えて、ここでのアプローチは必須でした。データを取得する方法と、ビューの準備をするためにデータを処理する方法について説明しました。

あなたが私が意味するものを見たいなら、あなたはこれを自分で試すことができます。スターウォーズのデータ​​には、http://swapi.co/で現在ホストされているRESTful APIがあります。先に進んで、そこでデータパーソンオブジェクトを構築してみてください。キーは少し異なる場合がありますが、APIエンドポイントは同じです。正確に6つのAPI呼び出しを行う必要があります。さらに、ビューに必要のない情報をオーバーフェッチする必要があります。

もちろん、これはこのデータ用のRESTful APIの1つの実装にすぎません。このビューを実装しやすくするより良い実装があります。たとえば、APIサーバーがネストされたリソースを実装し、人と映画の関係を理解し​​ている場合、映画データを次のように読み取ることができます。

GET-/ people / {id} / films

ただし、純粋なRESTful APIサーバーではほとんど実装されないため、バックエンドエンジニアにこのカスタムエンドポイントの作成を依頼する必要があります。それがRESTful APIのスケーリングの現実です。成長するクライアントのニーズを効率的に満たすためにカスタムエンドポイントを追加するだけです。このようなカスタムエンドポイントの管理は困難です。

ここで、GraphQLアプローチを見てみましょう。サーバー上のGraphQLは、カスタムエンドポイントのアイデアを取り入れ、それを極限まで高めます。サーバーは単一のエンドポイントであり、チャネルは重要ではありません。 HTTPでこれを行う場合、HTTPメソッドも重要ではありません。 / graphqlでHTTPを介して公開された単一のGraphQLエンドポイントがあるとします。

1回の往復で必要なデータを要求するため、サーバーのデータニーズ全体を表現する方法が必要になります。 GraphQLクエリを使用してこれを行います。

GETまたはPOST-/ graphql?query = {...}

GraphQLクエリは単なる文字列ですが、必要なすべてのデータを含める必要があります。これが宣言的な力の出番です。

英語でデータ要件を宣言する方法は次のとおりです。人の名前、生年、惑星の名前、すべての映画のタイトルが必要です。 GraphQLでは、これは次のように変換されます。

{
  person(ID:...){
    名、
    生年、
    惑星{
      名
    }、
    映画{
      タイトル
    }
  }
}

英語で表現された要件をもう一度読み、GraphQLクエリと比較します。できるだけ近くです。次に、このGraphQLクエリを、元​​のJSONデータと比較します。 GraphQLクエリは、JSONデータの正確な構造ですが、すべての「値」部分がないことを除きます。これを質問と回答の関係の観点から考えると、質問は回答部分のない回答文です。

回答文が次の場合:

太陽に最も近い惑星は水星です。

質問の適切な表現は、回答部分のない同じステートメントです。

(とは)太陽に最も近い惑星ですか?

GraphQLクエリにも同じ関係が適用されます。 JSON応答を取得し、すべての「回答」部分(値)を削除すると、そのJSON応答に関する質問を表すのに非常に適したGraphQLクエリが作成されます。

次に、GraphQLクエリを、データに対して定義した宣言型React UIと比較します。 GraphQLクエリのすべてがUIで使用され、UIで使用されるすべてがGraphQLクエリに表示されます。

これは、GraphQLの優れたメンタルモデルです。 UIは必要な正確なデータを認識しており、その要件の抽出はかなり簡単です。 GraphQLクエリの作成は、変数として使用されているものをUIから直接抽出するタスクです。

このモデルを逆にすると、それでも力が保持されます。 GraphQLクエリがある場合、クエリは応答と同じ「構造」になるため、UIでの応答の使用方法を正確に知っています。応答を調べて使用方法を知る必要はなく、APIに関するドキュメントは必要ありません。すべて組み込まれています。

Star Warsデータには、https://github.com/graphql/swapi-graphqlでホストされるGraphQL APIがあります。先に進んで、そこでデータパーソンオブジェクトを構築してみてください。いくつかの小さな違いがありますが、後で説明しますが、ビューのデータ要件を読み取るためにこのAPIに対して使用できる公式クエリは次のとおりです(例としてDarth Vaderを使用)。

{
  person(personID:4){
    名、
    生年、
    ホームワールド{
      名
    }、
    filmConnection {
      映画{
        タイトル
      }
    }
  }
}

このリクエストにより、ビューが使用したものに非常に近い応答構造が得られます。また、このデータすべてを1回のラウンドトリップで取得していることを忘れないでください。

GraphQLの柔軟性のコスト

完璧な解決策はおとぎ話です。 GraphQLが導入する柔軟性により、いくつかの明確な問題と懸念に扉が開きます。

GraphQLが簡単にする重要な脅威の1つは、リソース枯渇攻撃(サービス拒否攻撃)です。 GraphQLサーバーは、サーバーのすべてのリソースを消費する非常に複雑なクエリで攻撃される可能性があります。深いネストされた関係(ユーザー->友人->友人...)を照会したり、フィールドエイリアスを使用して同じフィールドを何度も要求したりするのは非常に簡単です。リソース枯渇攻撃はGraphQLに固有のものではありませんが、GraphQLを使用する場合は、それらに特に注意する必要があります。

ここでできる緩和策がいくつかあります。クエリのコスト分析を事前に行い、消費できるデータの量に何らかの制限を適用できます。また、タイムアウトを実装して、解決に時間がかかりすぎるリクエストを強制終了することもできます。また、GraphQLは単なる解決レイヤーであるため、GraphQLの下位レベルでレート制限の適用を処理できます。

保護しようとしているGraphQL APIエンドポイントが公開されておらず、自社のクライアント(Webまたはモバイル)の内部消費を目的としている場合、ホワイトリストアプローチを使用して、サーバーが実行できるクエリを事前承認できます。クライアントは、クエリの一意の識別子を使用して、事前に承認されたクエリを実行するようにサーバーに要求できます。 Facebookはこのアプローチを使用しているようです。

認証と承認は、GraphQLを使用する際に考慮する必要がある他の懸念事項です。 GraphQL解決プロセスの前、後、または処理中にそれらを処理しますか?

この質問に答えるには、GraphQLを独自のバックエンドデータフェッチロジック上のDSL(ドメイン固有言語)と考えてください。クライアントと実際のデータサービス(または複数のサービス)の間に配置できるのは1つのレイヤーにすぎません。

認証と承認を別のレイヤーと考えてください。 GraphQLは、認証または承認ロジックの実際の実装には役立ちません。それはそのためのものではありません。ただし、これらのレイヤーをGraphQLの背後に配置する場合は、GraphQLを使用して、クライアントと強制ロジック間でアクセストークンを通信できます。これは、RESTful APIを使用して認証と承認を行う方法と非常に似ています。

GraphQLがもう少し難しくするもう1つのタスクは、クライアントデータのキャッシュです。 RESTful APIは、辞書の性質上、キャッシュが簡単です。この場所はそのデータを提供します。キャッシュキーとして場所自体を使用できます。

GraphQLを使用すると、同様の基本的なアプローチを採用し、クエリテキストをキーとして使用して応答をキャッシュできます。しかし、このアプローチは制限されており、あまり効率的ではなく、データの一貫性に問題を引き起こす可能性があります。複数のGraphQLクエリの結果は簡単にオーバーラップする可能性があり、この基本的なキャッシュアプ​​ローチではオーバーラップは考慮されません。

ただし、この問題には素晴らしい解決策があります。グラフクエリとは、グラフキャッシュを意味します。 GraphQLクエリレスポンスをレコードのフラットコレクションに正規化し、各レコードにグローバルな一意のIDを与えると、完全なレスポンスをキャッシュする代わりに、それらのレコードをキャッシュできます。

ただし、これは単純なプロセスではありません。他のレコードを参照するレコードがあり、そこで循環グラフを管理します。キャッシュにデータを入力して読み取るには、クエリトラバーサルが必要です。キャッシュロジックを処理するレイヤーをコーディングする必要があります。ただし、この方法は全体的に、応答ベースのキャッシングよりもはるかに効率的です。 Relay.jsは、このキャッシュ戦略を採用し、内部で自動管理するフレームワークの1つです。

おそらくGraphQLで懸念する最も重要な問題は、一般にN + 1 SQLクエリと呼ばれる問題です。 GraphQLクエリフィールドはスタンドアロン関数として設計されており、これらのフィールドをデータベースのデータで解決すると、解決されたフィールドごとに新しいデータベースリクエストが発生する可能性があります。

シンプルなRESTful APIエンドポイントロジックの場合、構築されたSQLクエリを強化することにより、N + 1の問題を簡単に分析、検出、解決できます。 GraphQLで動的に解決されるフィールドの場合、それほど単純ではありません。幸いなことに、Facebookはこの問題の解決策の1つであるDataLoaderを開拓しています。

名前が示すように、DataLoaderはデータベースからデータを読み取り、GraphQLリゾルバー関数で使用できるようにするために使用できるユーティリティです。 SQLクエリを使用してデータベースから直接データを読み取る代わりに、DataLoaderを使用できます。DataLoaderは、データベースに送信する実際のSQLクエリを減らすエージェントとして機能します。

DataLoaderは、バッチ処理とキャッシュの組み合わせを使用してそれを実現します。同じクライアントリクエストでデータベースに複数のことを尋ねる必要が生じた場合、DataLoaderを使用してこれらの質問を統合し、データベースから回答をバッチロードできます。 DataLoaderは回答をキャッシュし、同じリソースに関する後続の質問で回答を利用できるようにします。

読んでくれてありがとう。

ReactまたはNodeを学習しますか?私の本をチェックアウト:

  • ビルドゲームでReact.jsを学ぶ
  • Node.jsの基本を超えて