GraphQL ロゴGraphQL

GraphQL と Relay におけるサブスクリプション

10/16/2015著者Dan Schafer と Laney Kuenzel

今年、GraphQL と Relay を発表しオープンソース化した際に、クエリによる読み取りと、ミューテーションによる書き込みの方法について説明しました。しかし、クライアントは、自身が関心を持つデータが変更された際に、サーバーから更新情報をプッシュで受信したいことがよくあります。それをサポートするために、GraphQL 仕様に3番目の操作であるサブスクリプションを導入しました。

イベントベースのサブスクリプション#

サブスクリプションに対する私たちの取り組みは、ミューテーションと同様です。サーバーがサポートするミューテーションのリストが、クライアントが実行できるすべての操作を記述するように、サーバーがサポートするサブスクリプションのリストは、サブスクライブできるすべてのイベントを記述します。クライアントがGraphQLの選択を使用してミューテーションを実行した後にサーバーにどのデータを再フェッチするかを指示できるのと同様に、クライアントはGraphQLの選択を使用して、サブスクリプションでどのデータをプッシュで受信するかをサーバーに指示できます。

たとえば、Facebookのスキーマには、クライアントが投稿に「いいね!」をするために使用できる`storyLike`というミューテーションフィールドがあります。クライアントは、「いいね!」の数と、「いいね!」文(「Dan と他の3人がこの投稿にいいね!しています。」など、様々な言語への翻訳はサーバー側で行います。これは、様々な言語での翻訳の複雑さによるものです。)を再フェッチしたい場合があります。そのためには、次のミューテーションを実行します。

mutation StoryLikeMutation($input: StoryLikeInput) {
storyLike(input: $input) {
story {
likers { count }
likeSentence { text }
}
}
}

しかし、投稿を見ているときには、他の誰かがその投稿に「いいね!」をしたときに、更新情報をプッシュで受信したいこともあります。そこでサブスクリプションが登場します。Facebookのスキーマには`storyLikeSubscribe`というサブスクリプションフィールドがあり、誰かがその投稿に「いいね!」または「いいね!」を取り消したときに、クライアントにデータをプッシュで送信できます。クライアントは、次のようなサブスクリプションを作成します。

subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
storyLikeSubscribe(input: $input) {
story {
likers { count }
likeSentence { text }
}
}
}

次に、クライアントは、サブスクライブしている投稿IDなどの情報を格納した`$input`変数の値とともに、このサブスクリプションをサーバーに送信します。

input StoryLikeSubscribeInput {
storyId: string
clientSubscriptionId: string
}

Facebookでは、このクエリをビルド時にサーバーに送信して一意のIDを生成し、そのサブスクリプションIDを含む特別なMQTTトピックを購読しますが、ここでは多くの異なるサブスクリプションメカニズムを使用できます。

サーバーでは、誰かが投稿に「いいね!」をするたびに、このサブスクリプションをトリガーします。すべてのクライアントがGraphQLを使用している場合、このフックをGraphQLミューテーションに配置できますが、GraphQL以外のクライアントも存在するため、常に発火するように、GraphQLミューテーションよりも下のレイヤーにフックを配置しています。

ライブクエリではないのはなぜですか?#

注目すべき点として、このアプローチでは、クライアントが関心のあるイベントをサブスクライブする必要があります。別のアプローチとして、クライアントがクエリをサブスクライブし、そのクエリの結果が変更されるたびに更新を要求する方法があります。なぜそのアプローチを取らなかったのでしょうか?

ストーリーに対して再フェッチしたいデータを見てみましょう。

fragment StoryLikeData on Story {
story {
likers { count }
likeSentence { text }
}
}

そのフラグメントでフェッチされたデータの変更をトリガーする可能性のあるイベントは何でしょうか?

  • 誰かが投稿に「いいね!」をする。
  • 誰かが投稿の「いいね!」を取り消す。
  • 投稿に「いいね!」をしていた人がアカウントを無効化する(「いいね!」数を1減らし、「いいね!」文の翻訳された数を減らす)。
  • 投稿に「いいね!」をしていた人がアカウントを有効化する(「いいね!」数を1増やし、「いいね!」文の翻訳された数を増やす)。
  • 投稿に「いいね!」をしていた人がブロックする(「いいね!」文に表示されない)。
  • 投稿に「いいね!」をしていた人が名前を変更する(「いいね!」文のテキストを更新する必要がある)。
  • 「いいね!」文における名前の順序付けに対する内部ランキングモデルが更新され、異なる人を最初にリストする必要がある(「いいね!」文のテキストを更新する必要がある)。

そして、これはイベントのほんの一部です。数千人が購読し、数百万人が投稿に「いいね!」をしている場合、これらのイベントはそれぞれ複雑になります。このデータセットに対するライブクエリの実装は非常に複雑であることが証明されました。

イベントベースのサブスクリプションを構築する場合、イベントによって明示的に定義されているため、どのイベントをトリガーする必要があるかを判断するという問題は容易です。また、既存のメッセージキューシステムの上に実装することも非常に簡単であることがわかりました。しかし、ライブクエリの場合、これははるかに困難であるように思われました。フィールドの値は、その解決関数の結果によって決定され、その関数の結果を変更する可能性のあるすべてのものを把握することは困難でした。理論的には、サーバーでポーリングしてこれを実装することはできますが、効率とタイムリーさに問題がありました。これに基づいて、イベントベースのサブスクリプションアプローチに投資することにしました。

今後の展開#

上記で説明したイベントベースのサブスクリプションアプローチを積極的に構築しています。そのアプローチを使用して、iOSとAndroidアプリでライブの「いいね!」とコメント機能を構築しており、その機能とAPIをさらに充実させています。Facebookでの現在の実装はFacebookのインフラストラクチャに結びついていますが、できるだけ早く進捗状況をオープンソース化することを楽しみにしています。

バックエンドとスキーマがライブクエリを容易にサポートしていないため、Facebookでライブクエリを開発する予定はありません。同時に、ライブクエリが実現可能であり、そのような状況で多くの価値を提供するバックエンドとスキーマがあることは明らかです。このトピックに関するコミュニティでの議論は素晴らしいものであり、そこからどのようなライブクエリの提案が出てくるのか楽しみにしています。!

サブスクリプションは、真に動的なアプリケーションを作成するための多くの可能性を生み出します。コミュニティの協力を得て、GraphQLとRelayを開発し続け、これらの可能性を実現できることを楽しみにしています。