Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コードで確認する、AWS Amplify API (GraphQL) Subscription...

コードで確認する、AWS Amplify API (GraphQL) Subscriptions によるリアルタイム通知のパターン #JAWSDAYS2021 #jawsdays2021_C

JAWS DAYS 2021
Track C 13:00-13:40
https://jawsdays2021.jaws-ug.jp/timetable/track-c-1300/

セッション内容
Amplify の API (GraphQL) では、 AppSync をベースとした Subscriptions 機能があります。 しかし、複数の機能・設定に関わる機能であるため、関連する記述がドキュメント上のあちこちにある状態です。
そのため、まとまった理解を得るためにやや手間がかかってしまいます。

このセッションでは AWS Amplify の API (GraphQL) Subscriptions に焦点を当てて話をしていきます。
具体的なコードを使い、シンプルな例からドキュメントに明記されていない使い方までを確認していくことで、 Subscriptions によるリアルタイム通知のパターンの理解を、一緒に深めていきたいと思います。

Kihara, Takuya

March 20, 2021
Tweet

More Decks by Kihara, Takuya

Other Decks in Technology

Transcript

  1. 自己紹介 名前 木原卓也 / @tacck 所属 株式会社ノースディテール コミュニティ ゆるWeb勉強会@札幌 (主催)

    Amplify Japan User Group (運営メンバー) 好きなAWSサービス Amazon Route 53 AWS Amplify 好きなフィギュアスケートの技 スプレッド・イーグル
  2. 話すこと AWS Amplify の API (GraphQL) 特に Subscription 機能とそれに関連するところ 実例として

    Vue.js (2.x系) によるコードの一部 サンプルコード: https://github.com/tacck/sample-amplify-subscriptions 上級者・開発者向けのトラックのため、 はしょる部分が多いところはご容赦ください。 わかりにくい部分は、ぜひ Twitter で指摘いただければ、 ブログやどこかの JAWS-UG 含む勉強会で補足してい きたいです。 発表できる場所あれば、どこへでも!(オンライン)
  3. 話さないこと AWS Amplify の詳細な機能 AWS Amplify の CLI の詳細な使い方 AWS

    Amplify を実際にサービスで使った場合の話 その他のサービス Vue.js の実装方法 上級者・開発者向けのトラックのため、 はしょる部分が多いところはご容赦ください。 わかりにくい部分は、ぜひ Twitter で指摘いただければ、 ブログやどこかの JAWS-UG 含む勉強会で補足してい きたいです。 発表できる場所あれば、どこへでも!(オンライン)
  4. 常時稼働インスタンス + サーバー機能実装 EC2 や ECS などを使って、インスタンス・コンテナを常時稼働。 WebSocket の制御・管理機能を、アプリケーションサーバー内に実装。 •

    大規模で利用率が常に高いのであれば、この選択。 • 多くのユースケースでは、過剰なインフラコストになりがち。 • インスタンス・ミドルウェア・コンテナのメンテナンスコストが必要。 • WebSocket クライアントの制御・管理機能をサーバー機能として実装必要。 本来作りたいサービスの機能「以外」の手間が大きい。
  5. API Gateway の WebSocket でサーバー機能実装 API Gateway + Lambda を使うことで、

    Serverless WebSocket 環境を構築可能。 WebSocket の制御・管理機能の一部を、 API Gateway が担う。 • 大規模で利用率が常に高いのであれば、この選択。 • 多くのユースケースでは、過剰なインフラコストになりがち。 • インスタンス・ミドルウェア・コンテナのメンテナンスコストが必要。 • WebSocket クライアントの制御・管理機能をサーバー機能として実装必要。 インフラ管理コストは低減できるが、 WebSocket クライアントの制御・管理機能の実装コストは引き続き残る。
  6. AWS AppSync GraphQL のフルマネージドサービス。 GraphQL の規格として Subscription 機能が存在。 • GraphQL

    を使い慣れていないと、導入にコストがかかる。 • リゾルバを VTL で定義できるが、 VTL 自体の学習コストがかかる。 AppSync / GraphQL の導入経験がないプロジェクトでは、 “手軽に” とはいかない。
  7. そこで AWS Amplify API (GraphQL) ライブラリによって、 AppSync を “手軽に” 利用可能。

    基本的な機能 (Query, Mutation, Subscription) を利用するための 標準的な GraphQL を自動生成してくれる。 開発者は JavaScript で呼び出すだけ。
  8. AWS Amplify で手軽に始められる AWS Amplify の API (GraphQL) 機能を使い Subscriptions

    を利用することで “手軽に” リアルタイムな通知を実装しよう!
  9. AWS Amplify の Subscriptions のパターン 1. Basic / @model ディレクティブのみ

    2. Filtering / @model + type Subscription 定義 3. Auth + Filtering / @model + @auth + type Subscription 定義 4. Multi-Auth + Filtering / 複数の認証先を利用して通知範囲を制御 5. Single Publisher / Lambda を利用した Subscription 制御
  10. 1.Basic / @model ディレクティブ もっとも基本的なパターン。 API (GraphQL) 機能を有効にしたら、すぐに使用可能。 書き込み内容 (Mutations)

    を、通知として受け取る。 例えば、誰でも(匿名で)書き込めるチャットサービスで、 誰かが書き込んだメッセージをすぐに画面に反映させる場合。 1.Basic
  11. $ amplify add api Initializing new Amplify CLI version... Done

    initializing new version. Scanning for plugins... Plugin scan successful ? Please select from one of the below mentioned services: GraphQL ? Provide API name: sampleamplifysubscri ? Choose the default authorization type for the API API key ? Enter a description for the API key: ? After how many days from now the API key should expire (1-365): 365 ? Do you want to configure advanced settings for the GraphQL API No, I am done. ? Do you have an annotated GraphQL schema? No ? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description) … 1.Basic CLI
  12. type OpenChat @model { id: ID! message: String! } schema.graphql

    export const onCreateOpenChat = /* GraphQL */ ` subscription OnCreateOpenChat { onCreateOpenChat { id message createdAt updatedAt } } `; src/graphql/subscriptions.js export const createOpenChat = /* GraphQL */ ` mutation CreateOpenChat( $input: CreateOpenChatInput! $condition: ModelOpenChatConditionInput ) { createOpenChat(input: $input, condition: $condition) { id message createdAt updatedAt } } `; src/graphql/mutations.js 1.Basic
  13. this.onCreateOpenChatSubscription = API.graphql( graphqlOperation(onCreateOpenChat), ).subscribe({ next: ({ provider, value })

    => { console.log({ provider, value }) this.subscriptionMessages.push(value.data.onCreateOpenChat) }, }) 1.Basic src/views/OpenChat.vue
  14. sendMessage: async function() { const message = await API.graphql( graphqlOperation(createOpenChat,

    { input: { message: this.inputMessage }, }), ) console.log(message) this.messages.push(message.data.createOpenChat) this.inputMessage = '' }, 1.Basic src/views/OpenChat.vue
  15. this.onCreateOpenChatSubscription = API.graphql( graphqlOperation(onCreateOpenChat), ).subscribe({ next: ({ provider, value })

    => { console.log({ provider, value }) this.subscriptionMessages.push(value.data.onCreateOpenChat) }, }) 1.Basic src/views/OpenChat.vue
  16. Filtering / @model + type Subscription 定義 基本的なパターン その2。 Basic

    の場合、すべての書き込み (Mutations) を、通知として受け取る。 そこで、引数として「どの情報を通知として受け取るか」を 指定することで、必要なものだけを受信できるようにする。 例えば、誰でも(匿名で)書き込めるチャットサービスで、 複数のチャットルームがある場合。 必要なのは、自分の開いているルームの最新メッセージのみ。 2.Filtering
  17. type RoomChat @model { id: ID! roomName: String! message: String!

    } type Subscription { onCreateRoomChatByRoomName(roomName: String!): RoomChat @aws_subscribe(mutations: ["createRoomChat"]) } schema.graphql 2.Filtering
  18. this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql( graphqlOperation(onCreateRoomChatByRoomName, { roomName: roomName }), ).subscribe({ next:

    ({ provider, value }) => { console.log({ provider, value }) this.subscriptionMessages[ value.data.onCreateRoomChatByRoomName.roomName ].push(value.data.onCreateRoomChatByRoomName) }, }) src/views/MultiRoomOpenChat.vue 2.Filtering
  19. sendMessage: async function() { const message = await API.graphql( graphqlOperation(createRoomChat,

    { input: { message: this.inputMessage, roomName: this.roomName }, }), ) console.log(message) this.messages[this.roomName].push(message.data.createRoomChat) this.inputMessage = '' }, src/views/MultiRoomOpenChat.vue 2.Filtering
  20. this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql( graphqlOperation(onCreateRoomChatByRoomName, { roomName: roomName }), ).subscribe({ next:

    ({ provider, value }) => { console.log({ provider, value }) this.subscriptionMessages[ value.data.onCreateRoomChatByRoomName.roomName ].push(value.data.onCreateRoomChatByRoomName) }, }) src/views/MultiRoomOpenChat.vue 2.Filtering
  21. 3.Auth + Filtering / @model + @auth + type Subscription

    定義 少し応用的なパターン。 特定の認証方法で認証されたユーザーのみが書き込みを行ない、 同様に認証されたユーザーが通知を受け取る。 例えば、会員制のサイト内でのメンバー間のチャットや、 メンバーとオペレーターのメッセージのやりとり。 3.Auth + Filtering
  22. type CloseRoomChat @model @auth(rules: [{ allow: owner, provider: userPools }])

    { id: ID! roomName: String! message: String! } type Subscription { onCreateCloseRoomChatByRoomName(roomName: String!): CloseRoomChat @aws_subscribe(mutations: ["createCloseRoomChat"]) } schema.graphql 3.Auth + Filtering
  23. this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql( graphqlOperation(onCreateCloseRoomChatByRoomName, { roomName: roomName, }), ).subscribe({ next:

    ({ provider, value }) => { console.log({ provider, value }) this.subscriptionMessages[ value.data.onCreateCloseRoomChatByRoomName.roomName ].push(value.data.onCreateCloseRoomChatByRoomName) }, }) src/views/CloseChat.vue 3.Auth + Filtering
  24. sendMessage: async function() { const message = await API.graphql( graphqlOperation(createCloseRoomChat,

    { input: { message: this.inputMessage, roomName: this.roomName }, }), ) src/views/CloseChat.vue 3.Auth + Filtering
  25. this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql( graphqlOperation(onCreateCloseRoomChatByRoomName, { roomName: roomName, }), ).subscribe({ next:

    ({ provider, value }) => { console.log({ provider, value }) this.subscriptionMessages[ value.data.onCreateCloseRoomChatByRoomName.roomName ].push(value.data.onCreateCloseRoomChatByRoomName) }, }) src/views/CloseChat.vue 3.Auth + Filtering
  26. type CloseRoomChat @model @auth( rules: [ { allow: owner, provider:

    userPools } { allow: public, provider: apiKey, operations: [read] } ] ) { id: ID! roomName: String! message: String! } type Subscription { onCreateCloseRoomChatByRoomName(roomName: String!): CloseRoomChat @aws_subscribe(mutations: ["createCloseRoomChat"]) @aws_api_key } schema.graphql 4.Multi-Auth + Filtering
  27. this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql({ query: onCreateCloseRoomChatByRoomName, variables: { roomName: roomName, },

    authMode: GRAPHQL_AUTH_MODE.API_KEY, }).subscribe({ next: ({ provider, value }) => { console.log({ provider, value }) this.subscriptionMessages[ value.data.onCreateCloseRoomChatByRoomName.roomName ].push(value.data.onCreateCloseRoomChatByRoomName) }, }) src/views/CloseChat.vue 4.Multi-Auth + Filtering
  28. sendMessage: async function() { const message = await API.graphql( graphqlOperation(createCloseRoomChat,

    { input: { message: this.inputMessage, roomName: this.roomName }, }), ) src/views/CloseChat.vue 4.Multi-Auth + Filtering
  29. this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql({ query: onCreateCloseRoomChatByRoomName, variables: { roomName: roomName, },

    authMode: GRAPHQL_AUTH_MODE.API_KEY, }).subscribe({ next: ({ provider, value }) => { console.log({ provider, value }) this.subscriptionMessages[ value.data.onCreateCloseRoomChatByRoomName.roomName ].push(value.data.onCreateCloseRoomChatByRoomName) }, }) src/views/CloseChat.vue 4.Multi-Auth + Filtering
  30. API_KEY で Subscription 登録 4.Multi-Auth + Filtering API.graphql({ query: …,

    variables: {…}, authMode: GRAPHQL_AUTH_MODE.API_KEY, })
  31. 4までの課題 1つの Mutation に対して、必ず 1つの通知が発生する。 10ユーザーが同時に書き込みをしたら? AppSync は 10の通知 を

    10ユーザーに(ほぼ)「同時」に送信 各ユーザーは 10の通知 を(ほぼ)「同時」に受信 つまり、ユーザー数が増えると、 AppSync が同時に送信すべき通知が 指数関数で増加する。 ユーザーの受信する通知は線形とはいえ、 マシン・ブラウザへの負荷に直結する。 5.Single Publisher
  32. 5.Single Publisher / Lambda を利用した Subscription 制御 Multi-Auth のさらに応用したなパターン。 通知を行なわない

    Mutation と、通知を行なう Mutation を用意し、 通知を行なう方のみユーザー認証を必要とする作りとする。 通知用の Mutation には、 @function (Lambda) を利用する。 例えば、多数の人が同時に利用・書き込みをし、 特定のユーザーが結果を全体へ定期的に通知する。 今回利用してもらった、デモのアンケートシステムがこれ。 5.Single Publisher
  33. type ResponseLateRoomChat @model(subscriptions: null) @auth( rules: [ { allow: owner,

    provider: userPools } { allow: public, provider: apiKey, operations: [read] } ] ) { id: ID! roomName: String! message: String! createdAt: AWSDateTime } schema.graphql 5.Single Publisher
  34. type Mutation { postResponses(input: PostResponsesInput): PostResponsesOutput @function(name: ”XXXXXXXXXX-${env}") } type

    Subscription { onPostResponses(roomName: String!): PostResponsesOutput @aws_subscribe(mutations: ["postResponses"]) @aws_api_key } schema.graphql 5.Single Publisher
  35. this.onPostResponses[roomName] = API.graphql({ query: onPostResponses, variables: { roomName: roomName, },

    authMode: GRAPHQL_AUTH_MODE.API_KEY, }).subscribe({ next: ({ provider, value }) => { console.log('onPostResponses', { provider, value }) value.data.onPostResponses.items.map(item => { this.subscriptionMessages[item.roomName].push(item) }) }, }) src/views/CloseChat.vue 5.Single Publisher
  36. this.interval = setInterval(async () => { const getStartTime = new

    Date(Date.now() - this.intervalTime) const postedMessages = await API.graphql({ query: listResponseLateRoomChats, variables: { filter: { createdAt: { ge: getStartTime.toISOString() } }, }, authMode: GRAPHQL_AUTH_MODE.API_KEY, }) console.log('interval listResponseLateRoomChats', postedMessages) if (postedMessages.data.listResponseLateRoomChats.items.length > 0) { this.rooms.forEach(roomName => { const messages = postedMessages.data.listResponseLateRoomChats.items.filter( item => item.roomName === roomName, ) if (messages && messages.length > 0) { this.postResponsesToAll(roomName, messages) } }) } }, this.intervalTime) src/views/ResponseLateRoomChatClient.vue 5.Single Publisher
  37. postResponsesToAll: async function(roomName, messages) { const message = await API.graphql(

    graphqlOperation(postResponses, { input: { roomName: roomName, items: messages, }, }), ) console.log('postResponsesToAll', message) }, src/views/ResponseLateRoomChatClient.vue 5.Single Publisher
  38. this.onPostResponses[roomName] = API.graphql({ query: onPostResponses, variables: { roomName: roomName, },

    authMode: GRAPHQL_AUTH_MODE.API_KEY, }).subscribe({ next: ({ provider, value }) => { console.log('onPostResponses', { provider, value }) value.data.onPostResponses.items.map(item => { this.subscriptionMessages[item.roomName].push(item) }) }, }) src/views/ResponseLateRoomChatClient.vue 5.Single Publisher
  39. AWS Amplify の Subscriptions のパターン 1. Basic / @model ディレクティブのみ

    2. Filtering / @model + type Subscription 定義 3. Auth + Filtering / @model + @auth + type Subscription 定義 4. Multi-Auth + Filtering / 複数の認証先を利用して通知範囲を制御 5. Single Publisher / Lambda を利用した Subscription 制御
  40. 発表者 タイトル 木村宏明氏 (株式会社スカラパートナーズ) バックエンドエンジニアがAmplifyで中規模案件に挑戦した話 青木 光平氏 (O: 株式会社 CTO)

    Amplify によるWeb&ネイティブアプリ開発とチーム運営 ララタン!!肉屋のCTO 谷 耕平 氏(株式会社ビータス CTO) toCサービスをAWS Amplify使って構築、運用しているお話 南島 康一 氏/向 宇 氏 (株式会社マーケットエンター プライズ Tech Lead/Front-end Engineer) レガシーなWebシステムにAmplifyを生やしてみた話 松井 英俊 氏 (株式会社スタートアップテクノロジー) Amplifyで作る配信サイト/ビデオチャットWebアプリ