AWS AppSyncでは、Mutationした時の通知をSubscriptionで受け取れます。
リアルタイムデータ - AWS AppSync
ただ、DynamoDBのレコードと同じ内容でMutationした場合でも、Subscriptionがどのように動作するのか気になったため、試してみた時のメモを残します。
目次
環境
Schema
今回用意したSchemaです。Mutationに
- createTodo
- updateTodo
の2つを用意しています。
今回は、onCreateTodoのSubscriptionがupdateTodoのMutationに反応しないかを確認するため、create/updateと名称を分けています。
ただし、GraphQL的にはSQLのInsert/Updateのような区別はありません。
type Todo { title: String! content: String } input TodoInput { title: String! content: String } type Mutation { createTodo(input: TodoInput!): Todo updateTodo(input: TodoInput!): Todo } type Subscription { onCreateTodo(title: String, content: String): Todo @aws_subscribe(mutations: ["createTodo"]) onUpdateTodo(title: String, content: String): Todo @aws_subscribe(mutations: ["updateTodo"]) }
リゾルバー
createTodo/updateTodoとも、リゾルバーのリクエストテンプレート/レスポンステンプレートは同じです。
リクエストテンプレート
{ "version": "2017-02-28", "operation": "PutItem", "key": { "title": { "S" : "${context.args.input.title}" } }, "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input) }
レスポンステンプレート
$util.toJson($context.result)
Amplifyを使ったアプリ
index.html
Mutation/Subscriptionするためのボタンを用意しています。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Amplify Framework</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div class="app"> <div>Subscribe: Create (title="foo", content="bar") <button id="subscribe-create">Subscribe (Create)</button> </div> <div>Subscribe: Update (title="foo", content="bar") <button id="subscribe-update">Subscribe (Update)</button> </div> <hr> <div>データ作成 <label for="title">タイトル</label> <input type="text" id="title" value="foo" /> <label for="content">内容</label> <input type="text" id="content" value="bar" /> </div> <div>Create button <button id="create">Create</button> </div> <div>Update button <button id="update">Update</button> </div> </div> <script src="main.bundle.js"></script> </body> </html>
app.js
ボタンのclickイベントで
- HTMLの内容でMutation
- Mutationに対するSubscription
が実行されるようにしています。
なお、Subscriptionについては、 unsubscribe()
を使っていないため、
- title = "foo"
- content = "bar"
な内容でMutaitonされると、毎回反応するようにしています。
import Amplify, { API } from 'aws-amplify'; import awsconfig from './aws-exports'; import * as mutations from "./graphql/mutations"; import * as subscriptions from "./graphql/subscriptions"; // データ作成 const createButton = document.getElementById('create'); createButton.addEventListener('click', () => { callMutation(mutations.createTodo, 'create'); }); // データ更新 const updateButton = document.getElementById('update'); updateButton.addEventListener('click', () => { callMutation(mutations.updateTodo, 'update'); }); // Mutation関数以外はすべて共通 const callMutation = (func, funcType) => { Amplify.configure(awsconfig); API.graphql( { query: func, authMode: 'API_KEY', variables: { input: { title: document.getElementById('title').value, content: document.getElementById('content').value, } } } ).then((data) => { console.log(`${funcType}: target data.`); console.log(data); }) .catch((e) => { console.log(`mutation(${funcType}) error`); console.log(e) }); }; // -------------------------- // Subscriptions // -------------------------- // CreateをSubscribe const subscribeCreateButton = document.getElementById('subscribe-create'); subscribeCreateButton.addEventListener('click', () => { callSubscription(subscriptions.onCreateTodo, 'subscribe(Create)'); }); // UpdateをSubscribe const subscribeUpdateButton = document.getElementById('subscribe-update'); subscribeUpdateButton.addEventListener('click', () => { callSubscription(subscriptions.onUpdateTodo, 'subscribe(Update)'); }); // Subscription関数以外は共通 const callSubscription = (func, funcType) => { Amplify.configure(awsconfig); console.log(`subscribe(${funcType}): start...`); const client = API.graphql( { query: func, authMode: 'API_KEY', variables: { title: "foo", content: "bar" } } ).subscribe({ next: (data) => { console.log(`subscribed: ${funcType}`); console.log(data); }, error: (err) => { console.log(err); console.log(`sub error (${funcType})`); }, close: () => console.log('sub done') }) };
動作確認
準備:Subscription開始
- createTodo
- updateTodo
のMutationに対してSubscriptionを実行しておきます。
新規登録時
Mutation createTodo()
の実行ボタンを押したしたところ、想定通り、Subscription onCreateTodo()
だけが反応しました。
上記を展開した内容はこちら。
同じ内容で更新時
同じ内容のまま、再度 Mutation createTodo()
の実行ボタンを押したところ、Subscription onCreateTodo()
が再度反応しました。
これより、特に設定をしなければ、同じ内容で更新した場合もSubscriptionで通知を受け取ることができるようです。
参考:createTodoは、同じキーで呼び出すとエラーにしたい
上記で見た通り、Mutation createTodo
はDynamoDBに存在するキーで実行してもエラーになりません。
ただ、新規登録用のMutationなど、DynamoDBに存在するキーで実行した時はエラーにしたい場合、リゾルバーのリクエストテンプレートに condition
を追加して制御します。
今回は attribute_not_exists
を使って制御します。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
{ "version": "2017-02-28", "operation": "PutItem", "key": { "title": { "S" : "${context.args.input.title}" } }, "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), ## 以降を追加 "condition": { "expression": "attribute_not_exists(#title)", "expressionNames": { "#title": "title" } } }
また、DynamoDBの状態は以下とします。
この時に、
- title = "foo"
- content = "ham"
にて、Mutation createTodo
するとエラー DynamoDB:ConditionalCheckFailedException
になります。
ただし、
- title = "foo"
- content = "bar"
の場合には、エラーとはなりません。
もう少し考える必要がありそうなものの、今回はこれくらいにします。
ソースコード
Githubに上げました。ディレクトリ subscription_for_same_update
の中が今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_AppSync_Amplify-sample