AWS AppSyncにて、同じ内容でMutationした場合に、Subscriptionがどうなるかを試してみた

AWS AppSyncでは、Mutationした時の通知をSubscriptionで受け取れます。
リアルタイムデータ - AWS AppSync

ただ、DynamoDBのレコードと同じ内容でMutationした場合でも、Subscriptionがどのように動作するのか気になったため、試してみた時のメモを残します。

 
目次

 

環境

  • AWS AppSync
  • aws-amplify 1.1.32

 

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を実行しておきます。

f:id:thinkAmi:20190726215322p:plain:w450

 

新規登録時

Mutation createTodo() の実行ボタンを押したしたところ、想定通り、Subscription onCreateTodo() だけが反応しました。

f:id:thinkAmi:20190726215411p:plain:w450

 
上記を展開した内容はこちら。

f:id:thinkAmi:20190726215515p:plain:w450

 

同じ内容で更新時

同じ内容のまま、再度 Mutation createTodo() の実行ボタンを押したところ、Subscription onCreateTodo() が再度反応しました。

f:id:thinkAmi:20190726215733p:plain:w450

 
これより、特に設定をしなければ、同じ内容で更新した場合も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の状態は以下とします。

f:id:thinkAmi:20190726220957p:plain:w300

 
この時に、

  • title = "foo"
  • content = "ham"

にて、Mutation createTodo するとエラー DynamoDB:ConditionalCheckFailedException になります。

f:id:thinkAmi:20190726221157p:plain:w450

 
ただし、

  • title = "foo"
  • content = "bar"

の場合には、エラーとはなりません。

f:id:thinkAmi:20190726221316p:plain:w450

 
もう少し考える必要がありそうなものの、今回はこれくらいにします。

 

ソースコード

Githubに上げました。ディレクトsubscription_for_same_update の中が今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_AppSync_Amplify-sample