2017年12月12日

GraphQLスキーマステッチングの説明: スキーマ委譲

GraphQLスキーマステッチングの理解(パートII)

GraphQL Schema Stitching explained

前回の記事では、リモート(実行可能)スキーマの内外について議論しました。これらのリモートスキーマは、スキーマステッチングと呼ばれるツールとテクニックのセットの基礎となります。

スキーマステッチングは、GraphQLコミュニティにおける全く新しいトピックです。一般的に、複数のGraphQLスキーマ(またはスキーマ定義)を組み合わせて接続し、単一のGraphQL APIを作成する行為を指します。

スキーマステッチングには、主に2つの主要な概念があります。

  • スキーマ委譲:スキーマ委譲の核となるアイデアは、特定のリゾルバーの呼び出しを別のリゾルバーに転送(委譲)することです。本質的に、スキーマ定義のそれぞれのフィールドが「再配線」されます。
  • スキーママージ:スキーママージは、2つ(またはそれ以上)の既存のGraphQL APIの和集合を作成するというアイデアです。これは、関与するスキーマが完全に分離している場合は問題ありません。そうでない場合は、名前の競合を解決する方法が必要です。

ほとんどの場合、委譲とマージは実際に一緒に使用され、両方を使用するハイブリッドアプローチになることに注意してください。この記事シリーズでは、各概念を十分に理解できるように、別々に扱います。

例:カスタムGitHub APIの構築

パブリックGitHub GraphQL APIに基づく例から始めましょう。Prisma GitHub組織に関する情報を提供する小さなアプリを構築したいと仮定します。

アプリに必要なAPIは、以下の機能を公開する必要があります。

  • Prisma組織に関する情報(IDメールアドレスアバターURL、またはピン留めされたリポジトリなど)を取得する
  • Prisma組織のリポジトリのリストを名前で取得する
  • アプリ自体に関する簡単な説明を取得する

QueryタイプをGitHubのGraphQLスキーマ定義から調べて、要件をスキーマのルートフィールドにどのようにマッピングできるかを確認しましょう。

要件1:Graphcool組織に関する情報を取得する

最初の機能であるPrisma組織に関する情報の取得は、QueryタイプのrepositoryOwnerルートフィールドを使用することで実現できます。

次のクエリを送信して、Prisma組織に関する情報を要求できます。

repositoryOwnerフィールドにloginとして"prismagraphql"を提供すると機能します。

ここでの1つの問題は、RepositoryOwneremailフィールドを持たないインターフェースであるため、emailを直接的に要求できないことです。ただし、Prisma組織の具体的なタイプが実際にOrganizationであることはわかっているので、クエリ内でインラインフラグメントを使用することでこの問題を回避できます。

わかりました。これで機能しますが、アプリの目的でGitHub GraphQL APIを直接的に使用できない摩擦点がすでにいくつかあります。

理想的には、APIは、すべてのクエリで引数を提供する必要がなく、Organizationのフィールドを直接要求できるようにするルートフィールドを公開するだけです。

要件2:Graphcoolリポジトリのリストを名前で取得する

2番目の要件であるGraphcoolリポジトリのリストを名前で取得する方法はどうでしょうか。Queryタイプをもう一度見ると、これは少し複雑になります。APIでは、リポジトリのリストを直接取得することはできません。代わりに、次のルートフィールドを使用して、ownerとリポジトリのnameを提供することで、単一のリポジトリを要求できます。

対応するクエリを次に示します。

ただし、アプリで実際に必要なのは(複数のリクエストを行う必要がないように)、次のようなルートフィールドです。

要件3:アプリ自体に関する簡単な説明を取得する

APIは、"This app provides information about the Prisma GitHub organization"のようなアプリを説明する文を返すことができる必要があります。

これはもちろん、GitHub APIに基づいて満たすことができない完全にカスタムの要件です。むしろ、単純なQueryルートフィールドを使用して、自分で実装する必要があることは明らかです。

アプリケーションスキーマの定義

APIに必要な機能と、スキーマに定義する必要がある理想的なQueryタイプを認識しました。

明らかに、このスキーマ定義自体は不完全です。OrganizationタイプとRepositoryタイプの定義がありません。この問題を解決する簡単な方法の1つは、GitHubのスキーマ定義から定義を手動でコピーアンドペーストすることです。

このアプローチはすぐに面倒になります。これらの型定義自体がスキーマ内の他の型に依存しているためです(たとえば、Repositoryタイプには、codeOfconductフィールドがあります。タイプはCodeOfConduct)。これも手動でコピーする必要があります。この依存関係チェーンがスキーマにどれだけ深く入り込むかに制限はなく、手動で完全なスキーマ定義をコピーすることになる可能性さえあります。

タイプを手動でコピーする場合、これを行う方法は3つあることに注意してください。

  • タイプ全体がコピーされ、追加のフィールドは追加されません
  • タイプ全体がコピーされ、追加のフィールドが追加されます(または既存のフィールドの名前が変更されます)
  • タイプのフィールドのサブセットのみがコピーされます

タイプ全体を単純にコピーする最初のアプローチが最も簡単です。これは、次のセクションで説明するように、graphql-importを使用して自動化できます。

タイプ定義に追加のフィールドが追加されたり、既存のフィールドの名前が変更されたりする場合は、対応するリゾルバーを実装する必要があります。もちろん、基盤となるAPIはこれらの新しいフィールドの解決を処理できません。

最後に、タイプのフィールドのサブセットのみをコピーすることを選択する場合があります。タイプの一部のフィールドを公開したくない場合(基盤となるスキーマには、アプリケーションスキーマで公開したくないUserタイプのpasswordフィールドがある場合があります)に、これは望ましい場合があります。

GraphQL型定義のインポート

パッケージgraphql-importを使用すると、.graphqlファイル間で型定義を共有できるため、手動作業から解放されます。次のように、別のGraphQLスキーマ定義からタイプをインポートできます。

JavaScriptコードで、importSchema関数を使用できるようになり、依存関係が解決され、スキーマ定義が完全になります。

APIの実装

上記のスキーマ定義では、まだ半分しか完了していません。まだ不足しているのは、リゾルバー関数の形式でのスキーマの実装です。

現時点で途方に暮れている場合は、GraphQLスキーマの基本的な仕組みと内部動作を紹介するこの記事を必ずお読みください。

これらのリゾルバーを実装する方法について考えてみましょう!最初のバージョンは次のようになります。

infoのリゾルバーは簡単です。アプリを説明する簡単な文字列を返すことができます。しかし、GitHub GraphQL APIから情報を実際に返す必要があるprismagraphqlおよびprismagraphqlRepositoriesのリゾルバーをどのように処理するのでしょうか。

ここでこれを実装するナイーブな方法は、info引数を見て、受信クエリの選択セットを取得することです。次に、同じ選択セットを持つ別のGraphQLクエリを最初から作成し、GitHub APIに送信します。これは、GitHub GraphQL APIのリモートスキーマを作成することで容易にすることもできますが、全体的にはまだ非常に冗長で面倒なプロセスです。

これはまさにスキーマ委譲が役立つ場所です!GitHubのスキーマが、(いくらか)要件のニーズに対応する2つのルートフィールド(repositoryOwnerとrepository)を公開していることを以前に確認しました。これを利用して、完全に新しいクエリを作成する手間を省き、代わりに受信クエリを転送できます。

別のスキーマへの委譲

したがって、完全に新しいクエリを構築しようとするのではなく、受信クエリを単純に取り込み、その実行を別のスキーマに委譲します。そこで使用するAPIは、delegateToSchemaと呼ばれ、graphql-toolsによって提供されます。

delegateToSchemaは、7つの引数を受け取ります(次の順序で)。

  1. schemaGraphQLSchemaの実行可能なインスタンス(これは、実行を委譲する*ターゲットスキーマ*です)
  2. fragmentReplacements:インラインフラグメントを含むオブジェクト(これは、この記事では議論しないより高度なケース向けです)
  3. operation:委譲するルートタイプを示す3つの値( "query" 、 "mutation" 、または "subscription" )のいずれかを含む文字列
  4. fieldName:委譲するルートフィールドの名前
  5. args:委譲するルートフィールドの入力引数
  6. context:ターゲットスキーマのリゾルバーチェーンを介して渡されるコンテキストオブジェクト
  7. info:委譲されるクエリに関する情報を含むオブジェクト

このアプローチを使用するには、最初にGitHub GraphQL APIを表すGraphQLSchemaの実行可能なインスタンスが必要です。これは、graphql-toolsのmakeRemoteExecutableSchemaを使用して取得できます。

GitHubのGraphQL APIは認証を必要とするため、これが機能するには認証トークンが必要です。これを取得するには、このガイドに従ってください。

GitHub APIのリモートスキーマを作成するには、次の2つのものが必要です。

  • そのスキーマ定義GraphQLSchemaインスタンスの形式)
  • そこからデータをフェッチする方法を知っているHttpLink

これは、次のコードを使用して実現できます。

GitHubLinkは、必要なLinkコンポーネントの作成に関する利便性を少し高める、HttpLinkの上に構築された単純なラッパーです。

素晴らしい。これで、リゾルバーで委譲できるGitHub GraphQL APIの実行可能なバージョンができました!🎉 まず、prismagraphqlリゾルバーを最初に実装しましょう。

delegateToSchema関数が期待する7つの引数を渡しています。全体として驚くべきことはありません。schemaは、GitHub GraphQL APIのリモート実行可能スキーマです。そこで、独自のprismagraphqlクエリの実行を、GitHub APIのrepositoryOwnerクエリに委譲します。そのフィールドはlogin引数を予期しているため、値として"prismagraphql"を提供します。最後に、infoオブジェクトとcontextオブジェクトをリゾルバーチェーンを通じて単純に渡します。

prismagraphqlRepositoriesのリゾルバーも同様のアプローチで実行できますが、少しトリッキーです。前の実装と異なる点は、prismagraphqlRepositories: [Repository!]!のタイプと、GitHubのスキーマ定義からの元のフィールドrepository: Repositoryが、以前ほどきれいに一致しないことです。単一のリポジトリの代わりに、リポジトリの配列を返す必要があります。

したがって、Promise.allを使用して、複数のクエリを一度に委譲し、それらの実行結果をプロミスの配列にバンドルできるようにします。

これで完了です。カスタムGraphQL APIの3つのリゾルバーをすべて実装しました。最初のもの(info用)は単純でカスタム文字列を返すだけですが、prismagraphqlprismagraphqlRepositoriesスキーマ委譲を使用して、クエリの実行を基盤となるGitHub APIに転送します。

このコードの動作例を確認したい場合は、このリポジトリを確認してください。

graphql-toolsを使用したスキーマ委譲

GitHubの上にカスタムGraphQL APIを構築する上記の例では、delegateToSchemaがクエリ実行のボイラープレートコードの作成からどのように解放してくれるかを見てきました。最初から新しいクエリを構築して、fetch、graphql-request、または他のHTTPツールで送信する代わりに、graphql-toolsによって提供されるAPIを使用して、クエリの実行を別の(実行可能な)GraphQLSchemaのインスタンスに委譲できます。都合の良いことに、このインスタンスはリモートスキーマとして作成できます。

高レベルでは、delegateToSchemaは、execute関数GraphQL.jsの「プロキシ」として単純に機能します。これは、内部的には、引数として渡された情報に基づいてGraphQLクエリ(またはミューテーション)を再構築することを意味します。クエリが構築されると、スキーマとクエリでexecuteを呼び出すだけです。

したがって、スキーマ委譲は、ターゲットスキーマがリモートスキーマであることを必ずしも必要としません。ローカルスキーマでも実行できます。その点で、スキーマ委譲は非常に柔軟なツールです。同じスキーマ内で委譲することもできます。これは基本的に、mergeSchemas from graphql-toolsで採用されているアプローチであり、複数のスキーマが最初に単一のスキーマにマージされ、次にリゾルバーが再配線されます。

本質的に、スキーマ委譲は、既存のGraphQL APIにクエリを簡単に転送できるようにすることです。

スキーマバインディング:GraphQL APIを再利用する簡単な方法

新しく習得したスキーマ委譲に関する知識を備えて、スキーマ委譲の上に構築された薄い便利なレイヤーにすぎない新しい概念であるスキーマバインディングを紹介できます。

パブリックGraphQL APIのバインディング

スキーマバインディングの核となるアイデアは、既存のGraphQL APIを再利用可能にする簡単な方法を提供し、他の開発者がNPM経由でプロジェクトにプルできるようにすることです。これにより、複数のGraphQL APIの機能を非常に簡単に組み合わせることができるGraphQL「ゲートウェイ」を構築する全く新しいアプローチが可能になります。

GitHub API専用のバインディングを使用すると、上記の例を簡略化できます。リモート実行可能スキーマを手動で作成する代わりに、この部分はgraphql-binding-githubパッケージによって実行されるようになりました。GitHub APIに委譲するために以前必要だったすべての初期設定コードが削除された、完全な実装は次のようになります。

リモートスキーマを自分で作成する代わりに、graphql-binding-githubからインポートされたGitHubクラスをインスタンス化し、そのdelegate関数を使用するだけです。次に、delegateToSchemaを内部で使用して、実際にリクエストを実行します。

パブリックGraphQL APIのスキーマバインディングは、開発者間で共有できます。graphql-binding-githubの他に、Yelp GraphQL APIのバインディングもすでに利用可能です。graphql-binding-yelp by Devan Beitel

自動生成された委譲関数

これらの種類のスキーマバインディングのAPIは、委譲関数が自動的に生成されるレベルまで改善できます。次のgithub.delegate('query', 'repository', ... )を記述する代わりに、バインディングは対応するルートフィールドにちなんで名付けられた関数を公開できます。github.query.repository( ... )

これらの委譲関数がビルドステップで、および強く型付けされた言語(TypeScriptやFlowなど)に基づいて生成される場合、このアプローチは、他のGraphQL APIと対話するためのコンパイル時型安全性も提供します!

このアプローチがどのように見えるかを垣間見るには、Graphcoolサービスのスキーマバインディングを簡単に生成でき、言及された委譲関数の自動生成のアプローチを使用するprisma-bindingリポジトリを確認してください。

まとめ

これは、「GraphQLスキーマステッチングの理解」シリーズの2番目の記事です。最初の記事では、ほとんどのスキーマステッチングシナリオの基礎となるリモート(実行可能)スキーマについて、いくつかの基礎を築き、学習しました。

この記事では、主にGitHub GraphQL APIに基づく包括的な例を提供することにより、スキーマ委譲の概念について議論しました(例のコードはこちらで入手できます)。スキーマ委譲は、リゾルバー関数の実行を、別の(または同じ)GraphQLスキーマ内の別のリゾルバーに転送(委譲)するメカニズムです。その主な利点は、完全に新しいクエリを最初から構築する必要がなく、代わりに受信クエリの(一部の)を再利用および転送できることです。

スキーマ委譲を基礎として使用すると、既存のGraphQL APIの再利用可能なスキーマバインディングを簡単に共有するための専用NPMパッケージを作成できます。これらがどのように見えるかを知るには、GitHub APIのバインディングと、任意のGraphcoolサービスのバインディングを簡単に生成できるprisma-bindingを確認できます。

次回の投稿をお見逃しなく!

Prismaニュースレターにサインアップしてください