Slide 1

Slide 1 text

コードで確認する、 AWS Amplify API (GraphQL) Subscriptions による リアルタイム通知のパターン 木原 卓也 / @tacck Track C / 13:00-13:40

Slide 2

Slide 2 text

自己紹介 名前 木原卓也 / @tacck 所属 株式会社ノースディテール コミュニティ ゆるWeb勉強会@札幌 (主催) Amplify Japan User Group (運営メンバー) 好きなAWSサービス Amazon Route 53 AWS Amplify 好きなフィギュアスケートの技 スプレッド・イーグル

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

話さないこと AWS Amplify の詳細な機能 AWS Amplify の CLI の詳細な使い方 AWS Amplify を実際にサービスで使った場合の話 その他のサービス Vue.js の実装方法 上級者・開発者向けのトラックのため、 はしょる部分が多いところはご容赦ください。 わかりにくい部分は、ぜひ Twitter で指摘いただければ、 ブログやどこかの JAWS-UG 含む勉強会で補足してい きたいです。 発表できる場所あれば、どこへでも!(オンライン)

Slide 5

Slide 5 text

人間は、 Webシステムで 手軽にリアルタイム通知をしたがる 生き物

Slide 6

Slide 6 text

リアルタイムな通知、”手軽に” 作れる? チャット的な仕組みをサイトに組み込んで、 ユーザーからの意見をすぐに受け取れるようにしたい。 多数のクライアントに、更新情報を一斉に通知したい。 ユーザーの利用状況を リアルタイムにダッシュボードで見たい。 Etc…

Slide 7

Slide 7 text

リアルタイムな通知、実装大変ですよね WebSocket で作ることになる場合が多い • EC2やECSなどの常時稼働インスタンス + サーバー機能実装 • API Gateway の WebSocket でサーバー機能実装 • AWS AppSync • AWS Amplify

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

AWS AppSync GraphQL のフルマネージドサービス。 GraphQL の規格として Subscription 機能が存在。 • GraphQL を使い慣れていないと、導入にコストがかかる。 • リゾルバを VTL で定義できるが、 VTL 自体の学習コストがかかる。 AppSync / GraphQL の導入経験がないプロジェクトでは、 “手軽に” とはいかない。

Slide 11

Slide 11 text

そこで AWS Amplify API (GraphQL) ライブラリによって、 AppSync を “手軽に” 利用可能。 基本的な機能 (Query, Mutation, Subscription) を利用するための 標準的な GraphQL を自動生成してくれる。 開発者は JavaScript で呼び出すだけ。

Slide 12

Slide 12 text

AWS Amplify で手軽に始められる AWS Amplify の API (GraphQL) 機能を使い Subscriptions を利用することで “手軽に” リアルタイムな通知を実装しよう!

Slide 13

Slide 13 text

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 制御

Slide 14

Slide 14 text

1.Basic / @model ディレクティブ

Slide 15

Slide 15 text

1.Basic / @model ディレクティブ もっとも基本的なパターン。 API (GraphQL) 機能を有効にしたら、すぐに使用可能。 書き込み内容 (Mutations) を、通知として受け取る。 例えば、誰でも(匿名で)書き込めるチャットサービスで、 誰かが書き込んだメッセージをすぐに画面に反映させる場合。 1.Basic

Slide 16

Slide 16 text

$ 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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Subscription 登録 1.Basic API.graphql(graphqlOperation(onCreateOpenChat))

Slide 22

Slide 22 text

Mutation 実行 1.Basic API.graphql(graphqlOperation(createOpenChat,{…})

Slide 23

Slide 23 text

リアルタイムに通知 subscribe({next: ({ provider, value }) => {…}}) 1.Basic

Slide 24

Slide 24 text

2.Filtering / @model + type Subscription 定義

Slide 25

Slide 25 text

Filtering / @model + type Subscription 定義 基本的なパターン その2。 Basic の場合、すべての書き込み (Mutations) を、通知として受け取る。 そこで、引数として「どの情報を通知として受け取るか」を 指定することで、必要なものだけを受信できるようにする。 例えば、誰でも(匿名で)書き込めるチャットサービスで、 複数のチャットルームがある場合。 必要なのは、自分の開いているルームの最新メッセージのみ。 2.Filtering

Slide 26

Slide 26 text

type RoomChat @model { id: ID! roomName: String! message: String! } type Subscription { onCreateRoomChatByRoomName(roomName: String!): RoomChat @aws_subscribe(mutations: ["createRoomChat"]) } schema.graphql 2.Filtering

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Subscription 登録 API.graphql(graphqlOperation(onCreateRoomChatByRoomName, { roomName: ‘Room1’ })) API.graphql(graphqlOperation(onCreateOpenChat, { roomName: ‘Room2’ })) 2.Filtering

Slide 31

Slide 31 text

Mutation 実行 API.graphql(graphqlOperation(createRoomChat, { input: { message: this.inputMessage, roomName: ‘Room1’}}) 2.Filtering

Slide 32

Slide 32 text

Room1 のみ受信可能 2.Filtering subscribe({next: ({ provider, value }) => {…}})

Slide 33

Slide 33 text

3.Auth + Filtering / @model + @auth + type Subscription 定義

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

sendMessage: async function() { const message = await API.graphql( graphqlOperation(createCloseRoomChat, { input: { message: this.inputMessage, roomName: this.roomName }, }), ) src/views/CloseChat.vue 3.Auth + Filtering

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

認証済みユーザーのみSubscription 登録可能 API.graphql(graphqlOperation( onCreateCloseRoomChatByRoomName, { roomName: ‘Room1’ } )) 3.Auth + Filtering

Slide 40

Slide 40 text

認証済みユーザーで Mutation 実行 API.graphql(graphqlOperation( createCloseRoomChat, { input: { message: this.inputMessage, roomName: ‘Room1’} } ) 3.Auth + Filtering

Slide 41

Slide 41 text

認証済みユーザーのみ受信可能 subscribe({next: ({ provider, value }) => {…}}) 3.Auth + Filtering

Slide 42

Slide 42 text

4.Multi-Auth + Filtering / 複数の認証を利用して通知範囲を制御

Slide 43

Slide 43 text

4.Multi-Auth + Filtering / 複数の認証を利用して通知範囲を制御 応用的なパターン。 特定の認証方法で認証されたユーザーのみが書き込みを行ない、 別の認証方法で認証されたユーザーが通知を受け取る。 つまり、機能と権限の調整を行なうことができる。 例えば、書き込みは認証済みユーザーのみ可能だが、 読み込みや通知の受信は誰でもできるような掲示板サイト。 4.Multi-Auth + Filtering

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

API_KEY で Subscription 登録 4.Multi-Auth + Filtering API.graphql({ query: …, variables: {…}, authMode: GRAPHQL_AUTH_MODE.API_KEY, })

Slide 49

Slide 49 text

認証済みユーザーで Mutation 実行 4.Multi-Auth + Filtering API.graphql(graphqlOperation( createCloseRoomChat, { input: { message: this.inputMessage, roomName: ‘Room1’ } } )

Slide 50

Slide 50 text

全ユーザーが受信可能 4.Multi-Auth + Filtering subscribe({ next: ({ provider, value }) => {…}})

Slide 51

Slide 51 text

5.Single Publisher / Lambda を利用した Subscription 制御

Slide 52

Slide 52 text

4までの課題 1つの Mutation に対して、必ず 1つの通知が発生する。 10ユーザーが同時に書き込みをしたら? AppSync は 10の通知 を 10ユーザーに(ほぼ)「同時」に送信 各ユーザーは 10の通知 を(ほぼ)「同時」に受信 つまり、ユーザー数が増えると、 AppSync が同時に送信すべき通知が 指数関数で増加する。 ユーザーの受信する通知は線形とはいえ、 マシン・ブラウザへの負荷に直結する。 5.Single Publisher

Slide 53

Slide 53 text

5.Single Publisher / Lambda を利用した Subscription 制御 Multi-Auth のさらに応用したなパターン。 通知を行なわない Mutation と、通知を行なう Mutation を用意し、 通知を行なう方のみユーザー認証を必要とする作りとする。 通知用の Mutation には、 @function (Lambda) を利用する。 例えば、多数の人が同時に利用・書き込みをし、 特定のユーザーが結果を全体へ定期的に通知する。 今回利用してもらった、デモのアンケートシステムがこれ。 5.Single Publisher

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

API.graphql( graphqlOperation(createResponseLateRoomChat, { input: { message: this.inputMessage, roomName: this.roomName }, }), ) src/views/ResponseLateRoomChatClient.vue 5.Single Publisher

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

@function の方を Subscription 登録 5.Single Publisher API.graphql({ query: onPostResponses, variables: {…}, authMode: GRAPHQL_AUTH_MODE.API_KEY, })

Slide 62

Slide 62 text

登録用 Mutation 実行 API.graphql( graphqlOperation(createResponseLateRoomChat, { input: { message: this.inputMessage, roomName: this.roomName }, }), ) 5.Single Publisher

Slide 63

Slide 63 text

通知用 データを Query で取得 API.graphql({ query: listResponseLateRoomChats, variables: {…}, authMode: GRAPHQL_AUTH_MODE.API_KEY, }) 5.Single Publisher

Slide 64

Slide 64 text

通知用 Mutation 実行 API.graphql( graphqlOperation(postResponses, { input: { roomName: roomName, items: messages } }) ) 5.Single Publisher

Slide 65

Slide 65 text

コントロールされた通知を受信 subscribe({ next: ({ provider, value }) => {…}}) 5.Single Publisher

Slide 66

Slide 66 text

まとめ

Slide 67

Slide 67 text

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 制御

Slide 68

Slide 68 text

もっと AWS Amplify を知りたくなったら

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

発表者 タイトル 木村宏明氏 (株式会社スカラパートナーズ) バックエンドエンジニアがAmplifyで中規模案件に挑戦した話 青木 光平氏 (O: 株式会社 CTO) Amplify によるWeb&ネイティブアプリ開発とチーム運営 ララタン!!肉屋のCTO 谷 耕平 氏(株式会社ビータス CTO) toCサービスをAWS Amplify使って構築、運用しているお話 南島 康一 氏/向 宇 氏 (株式会社マーケットエンター プライズ Tech Lead/Front-end Engineer) レガシーなWebシステムにAmplifyを生やしてみた話 松井 英俊 氏 (株式会社スタートアップテクノロジー) Amplifyで作る配信サイト/ビデオチャットWebアプリ

Slide 71

Slide 71 text

No content