🙋‍♂️

GraphQLにおけるSubscription処理について(実装例: Amplify + AppSync)

2021/03/01に公開

はじめに

  • こんにちは ! KDDI アジャイル開発センターの小板橋です。
  • この記事は、KDDI Engineer Advent Calendar 2020の15日目の記事となります。
  • GraphQLの記事はよく見かけますが、GraphQLにおけるSubscription処理についてはあまり目にすることがありません。そこで本記事は、Subscriptionに着目したもので、実装時の助けになれれば幸いです。
  • GraphQLについては、以前事細かく書いたので、そちらをご覧ください。GraphQL入門

GraphQLにおけるSubscription処理

仕組み

realtime API

pub/subのような仕組みのAPIをrealtime APIというらしいです。
参考: GraphQL Subscription

このrealtime APIなのですが、大きく分けて3つのタイプに分類されます。

1.ポーリング
クライアントは、定期的にリクエストを発行し、関心のあるデータの状態を確認します。
ポーリングの特徴としては、調整が困難なことです。というのも、更新の頻度が低い場合は、ポーリングは無駄になります。逆に、更新が頻繁に行われると、ポーリングによる待ち時間が長くなります。

2.イベントベースのサブスクリプション
クライアントは、1つ以上のイベントを処理することをサーバーに通知します。これらのイベントがトリガーされるたびに、サーバーはクライアントに通知します。
このモデルでは、サーバーがイベントを識別し、それらを事前に通知する方法を特定する必要があります。

3.ライブクエリ
クライアントは、クエリを発行します。クエリへの返答が変わるたびに、サーバーは新しいデータをクライアントにプッシュします。
ライブクエリとイベントベースのサブスクリプションの主な違いは、ライブクエリはイベントの概念に依存しないことです。
データ自体はライブであり、変更を通知する方法が含まれています。

ではGraphQLのSubscriptionは??

GraphQLのSubscriptionを使用すると、クライアントはサーバーにGraphQLのクエリとクエリ変数を送信します。
サーバーは、これらの入力を特定のイベント処理にマップし、イベントがトリガーされたときにクエリを実行します。

Pub/Subの仕組み(ライフサイクル)

Pub/Subのライフサイクルなのですが、次のようになります。

  • Subscribeの設定
    → クライアントは、クエリとそのクエリ変数をサーバーに送信することにより、Subscriptionを初期化します。Subscriptionが作成されると、クエリとクエリ変数は、Subscriptionを設定しているイベントにマップされます。

  • Subscribeの解除
    → Subscriptionの登録を解除すると、ペイロードを受信しなくなります。
    これは、クライアントが明示的にサブスクライブを解除した場合、またはサーバーがサブスクライブの解除を行う必要があると判断した場合、または、クライアントが切断された場合に発生する可能性があります。
    スクリーンショット 2020-12-14 21.41.31.png

  • Publishの実行
    → Subscriptionに関連付けられたイベントがトリガーされると、Subscriptionはクエリ、クエリ変数、およびペイロードを実行し、結果をクライアントに送信します。
    スクリーンショット 2020-12-14 21.43.11.png

実装する

  • 今回はAWSのAppSync + Client側はAmplifyを利用した場合の、Subscription処理を実装していきます。

AppSyncにおけるSubscription処理

AppSyncのSubscriptionは、ミューテーションに対する応答として呼び出される形になります。
その為、スキーマのミューテーションで指定することで、AppSyncで任意のデータソースをリアルタイム対応にすることができます。
また、AppSyncの便利なところは、AppSyncクライアントのSDKは、Subscriptionの接続管理を自動的に処理するところです。
ちなみに、クライアントとサービス間のネットワークプロトコルとして、WebSocketまたはMQTT over WebSocketsのいずれかを使用するようです。

スキーマの設定方法

例えば下記のようなミューテーションがあったとします。

type Post {
 id: ID!
 user: String!
}

input CreatePostInput {
 id: ID
 user: String!
}

type Mutation {
 createPost(input: CreatePostInput!): Post
}

type Subscription {
    CreatePostInput: Post
}

そして、通知を受け取る各サブスクリプションに @aws_subscribe(mutations: ["mutation_field_1", "mutation_field_2"]) ディレクティブを追加することで、これらのフィールドをリアルタイムに対応できます。

type Subscription {
    addedPost: Post
    @aws_subscribe(mutations: ["CreatePostInput"])
}

これで、AppSync側の設定は良いのですが、気をつけなければならないのは、Client側の実装です。

Client側の実装

まず重要なことは、client側のSubscriptionの定義の仕方によって欲しいデータを定義になければならない点です。上記の例ですと、下記のようになります。

subscription CreatePostInputSub {
    CreatePostInput {
        id
        user
    }
}

Amplify GraphQLクライアントの使用方法

  • AppSyncのSubscriptionを使用する場合は、aws-exports.ts(js)のAppSync設定を下記のように設定します。
Amplify.configure({
  Auth: {
    identityPoolId: 'xxx',
    region: 'xxx' ,
    cookieStorage: {
      domain: 'xxx',
      path: 'xxx',
      secure: true
    }
  },
  aws_appsync_graphqlEndpoint: 'xxxx',
  aws_appsync_region: 'xxxx',
  aws_appsync_authenticationType: 'xxxx',
  aws_appsync_apiKey: 'xxxx'
});

  • 実際のコードとしては下記のようになります。
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import * as subscriptions from './graphql/subscriptions';

const subscription = API.graphql(
    graphqlOperation(subscriptions.onCreateTodo)
).subscribe({
    next: ({ provider, value }) => console.log({ provider, value })
});

Subscriptionにより取得したデータを利用しclient側で処理を行いたい場合は、subscribeにより取得した引数のvalueを使い回すようにすれば良いのです。

subscribe({
    next: ({ provider, value }) => console.log({ provider, value })
})

おまけ

ちなみに上の実装だと、tsを利用している場合、GraphQLにおけるSubscriptionの型で怒られます。。
それを回避する方法を下記に残しておきます。
Observableについては、また次回別記事で書いていこうと思います。

import Observable from 'zen-observable'

const subscription = API.graphql(graphqlOperation(subscriptions.onCreateTodo) as Observable<object>

引用元

GraphQL Subscriptions
AWS公式ドキュメント
Amplifyドキュメント

Discussion