AppSyncでDynamoDBのトランザクション処理

# はじめに
この記事は [NTTテクノクロスAdvent Calendar 2022](https://qiita.com/advent-calendar/2022/ntt-tx) の 4日目の記事です。

NTTテクノクロスの井上です。
普段はAWS関連の業務をしています。

この記事では、
AppSyncでDynamoDBのトランザクション処理をおこなう方法についてご紹介します。

# 目次
1. [構成](#1-構成)
1. [DynamoDB](#2-DynamoDB)
1. [IAM](#3-IAM)
1. [AppSync](#4-AppSync)
1. [実行](#5-実行)

# 構成
作成するアーキテクチャはこのような図になります。

DynamoDBの2つのテーブルに、同時にデータを書き込む処理を作ります。

![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2318120/60f3de2e-fd76-d033-1e15-26030e13e631.png)

# DynamoDB
この記事で扱うテーブルの定義はこちらの通りです。
- FooTable

|カラム名|型||
|---|---|---|
|ID|String|パーティションキー|
|foo|String|-|

- BarTable

|カラム名|型||
|---|---|---|
|ID|String|パーティションキー|
|bar|String|-|

# IAM
AppSyncにDynamoDBの権限を付与するためにロールを作成します。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:<アカウントID>:table/FooTable",
                "arn:aws:dynamodb:ap-northeast-1:<アカウントID>:table/FooTable/*",
                "arn:aws:dynamodb:ap-northeast-1:<アカウントID>:table/BarTable",
                "arn:aws:dynamodb:ap-northeast-1:<アカウントID>:table/BarTable/*"
            ]
        }
    ]
}
```

# AppSync
## データソース
データソースを作成します。

テーブル名は、トランザクションオペレーションのリクエストマッピングテンプレートで指定するので、何でも構いません。

|項目|設定値|
|---|---|
|データソース名|FooTable|
|データソースタイプ|Amazon DynamoDB テーブル|
|リージョン|AP-NORTHEAST-1|
|テーブル名|FooTable|
|既存のロールを作成または使用する|先ほど作成したIAMロールを選択|

## スキーマ
スキーマを作成します。

この例では、次のことをおこなうために``TransactWriteItems``を使用します。
- FooTableにfooデータを登録・更新
- BarTableにbarデータを登録・更新

```graphql
type Bar {
	id: String!
	bar: String
}

input BarInput {
	id: String!
	bar: String
}

type Foo {
	id: String!
	foo: String
}

input FooInput {
	id: String!
	foo: String
}

type Mutation {
	transactTests(Bar: [BarInput], Foo: [FooInput]): TransactionResult
}

type Query {
	getFoo(id: String!): Foo
	getBar(id: String!): Bar
}

type TransactionResult {
	foo: [Foo]
	bar: [Bar]
}

schema {
	query: Query
	mutation: Mutation
}
```

## リゾルバー
``transactTests``のリゾルバーを作成します。

データソースには先ほど作成した``FooTable``を選択します。

### リクエストマッピングテンプレート
```vtl
#set($BarTransactPutItems = [])
#set($index = 0)
#foreach($Bar in ${ctx.args.Bar})
  #set($keyMap = {})
  $util.qr($keyMap.put("id", $util.dynamodb.toString($Bar.id)))
  #set($attributeValues = {})
  $util.qr($attributeValues.put("bar", $util.dynamodb.toString($Bar.bar)))
  #set($index = $index + 1)
  #set($BarTransactPutItem = {"table": "BarTable",
      "operation": "PutItem",
      "key": $keyMap,
      "attributeValues": $attributeValues})
  $util.qr($BarTransactPutItems.add($BarTransactPutItem))
#end

#set($FooTransactPutItems = [])
#set($index = 0)
#foreach($Foo in ${ctx.args.Foo})
  #set($keyMap = {})
  $util.qr($keyMap.put("id", $util.dynamodb.toString($Foo.id)))
  #set($attributeValues = {})
  $util.qr($attributeValues.put("foo", $util.dynamodb.toString($Foo.foo)))
  #set($index = $index + 1)
  #set($FooTransactPutItem = {"table": "FooTable",
      "operation": "PutItem",
      "key": $keyMap,
      "attributeValues": $attributeValues})
  $util.qr($FooTransactPutItems.add($FooTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($BarTransactPutItems))
$util.qr($transactItems.addAll($FooTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```
### レスポンスマッピングテンプレート
```vtl
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($bar = [])
$util.qr($bar.add(${ctx.result.keys[0]}))

#set($foo = [])
$util.qr($foo.add(${ctx.result.keys[1]}))

#set($transactionResult = {})
$util.qr($transactionResult.put('bar', $bar))
$util.qr($transactionResult.put('foo', $foo))

$util.toJson($transactionResult)
```

# 実行
実際にクエリを打って正しく動くか試してみます。

- クエリ
左側が送ったクエリ、右側が返ってきた値です。
![6-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2318120/9ebb54b3-dc77-ee21-4b71-1ad9d4b7417d.png)

- FooTable
![6-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2318120/c50247f8-0734-fbaa-13b5-2177caece45a.png)

- BarTable
![6-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2318120/78d82c3a-a27e-e051-8890-3e32304d7d0a.png)

2つのテーブルに正しく登録されたことを確認できました。

# おわりに
今回は、AppSyncでDynamoDBのトランザクション処理をおこなう方法をご紹介しました。
少しでも参考にしていただけたら嬉しいです。

では、[NTTテクノクロスAdvent Calendar 2022](https://qiita.com/advent-calendar/2022/ntt-tx) の 5日目も、お楽しみください！

# 参考
- [チュートリアル: DynamoDB トランザクションリゾルバー - AWS公式](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/tutorial-dynamodb-transact.html)