前回の記事では、リモート(実行可能)スキーマの内外について議論しました。これらのリモートスキーマは、スキーマステッチングと呼ばれるツールとテクニックのセットの基礎となります。
スキーマステッチングは、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つの問題は、RepositoryOwner
はemail
フィールドを持たないインターフェースであるため、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つの引数を受け取ります(次の順序で)。
schema
:GraphQLSchemaの実行可能なインスタンス(これは、実行を委譲する*ターゲットスキーマ*です)fragmentReplacements
:インラインフラグメントを含むオブジェクト(これは、この記事では議論しないより高度なケース向けです)operation
:委譲するルートタイプを示す3つの値( "query" 、 "mutation" 、または "subscription" )のいずれかを含む文字列fieldName
:委譲するルートフィールドの名前args
:委譲するルートフィールドの入力引数context
:ターゲットスキーマのリゾルバーチェーンを介して渡されるコンテキストオブジェクト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
用)は単純でカスタム文字列を返すだけですが、prismagraphql
とprismagraphqlRepositories
はスキーマ委譲を使用して、クエリの実行を基盤となる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ニュースレターにサインアップしてください