

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# AWS AppSync での DynamoDB トランザクションの実行
<a name="tutorial-dynamodb-transact-js"></a>

AWS AppSync は、1 つのリージョン内の 1 つ以上のテーブルで Amazon DynamoDB トランザクションオペレーションの使用をサポートしています。サポートされている処理は、`TransactGetItems` および `TransactWriteItems` です。 AWS AppSync でこれらの機能を使用すると、次のようなタスクを実行できます。
+ 単一クエリでキーのリストを渡し、テーブルからの結果を返す
+ 単一クエリで 1 つ以上のテーブルからレコードを読み取る
+ トランザクション内のレコードを 1 つまたは複数のテーブルにオールオアナッシング方式で書き込みます
+ 一部の条件が満たされたときにトランザクションを実行します

## アクセス許可
<a name="permissions-js"></a>

他のリゾルバーと同様に、 AWS AppSync でデータソースを作成し、ロールを作成するか、既存のロールを使用する必要があります。トランザクションオペレーションでは DynamoDB テーブルごとに異なるアクセス許可が必要であるため、読み取りまたは書き込みアクションのために設定されたアクセス許可がロールに必要です。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME",
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME/*"
            ]
        }
    ]
}
```

------

**注記**  
ロールは AWS AppSync のデータソースに関連付けられ、フィールドのリゾルバーはデータソースに対して呼び出されます。DynamoDB に対して取得するよう設定されたデータソースは、設定を単純にするために 1 つのテーブルのみ指定されます。したがって、単一のリゾルバーで複数のテーブルに対してトランザクションオペレーションを実行する場合、これはより高度なタスクであり、リゾルバーが利用するすべてのテーブルへのアクセスをデータソースのロールに許可する必要があります。これは、上記の IAM ポリシーの **Resource** フィールドで行われます。テーブルに対するトランザクション呼び出しの設定は、リゾルバーのテンプレートで行います。これについては以下で説明します。

## データソース
<a name="data-source-js"></a>

分かりやすくするために、このチュートリアルではすべてのリゾルバーに同じデータソースを使用します。

どちらもパーティションキーとして `accountNumber` を持つ **savingAccounts** と **checkingAccounts** という 2 つテーブルと、パーティションキーとして `transactionId` を持つ **transactionHistory** というテーブルができます。次の CLI コマンドをと共に使用して、テーブルを作成できます。必ずリージョンを `region` に置き換えてください。

**CLI を使用する**

```
aws dynamodb create-table --table-name savingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name checkingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name transactionHistory \
  --attribute-definitions AttributeName=transactionId,AttributeType=S \
  --key-schema AttributeName=transactionId,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region
```

 AWS AppSync コンソールの**データソース**で、新しい DynamoDB データソースを作成し、**TransactTutorial** という名前を付けます。テーブルとして **savingAccounts** を選択します (ただし、トランザクションを使用する場合は特定のテーブルは関係ありません)。[新規] を選択して、新しいロールとデータソースを作成します。データソースの設定を確認して、生成されたロールの名前を確認できます。IAM コンソールでは、データソースがすべてのテーブルを操作できるようにするインラインポリシーを追加できます。

`region` および `accountID` をお客様のリージョンとアカウント ID に置き換えます。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory/*"
            ]
        }
    ]
}
```

------

## トランザクション
<a name="transactions-js"></a>

この例では、コンテキストは従来の銀行取引で、次の目的で `TransactWriteItems` を使用します。
+ 普通預金から当座預金への振替
+ 取引ごとの新しい取引レコードの生成

次に、`TransactGetItems` を使用して、普通預金と当座預金の詳細を取得します。

**警告**  
`TransactWriteItems` は、競合の検出と解決とともに使用する場合はサポートされていません。これらの設定は、起こり得るエラーを防ぐために無効にする必要があります。

次のように GraphQL スキーマを定義します。

```
type SavingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type CheckingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type TransactionHistory {
    transactionId: ID!
    from: String
    to: String
    amount: Float
}

type TransactionResult {
    savingAccounts: [SavingAccount]
    checkingAccounts: [CheckingAccount]
    transactionHistory: [TransactionHistory]
}

input SavingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input CheckingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input TransactionInput {
    savingAccountNumber: String!
    checkingAccountNumber: String!
    amount: Float!
}

type Query {
    getAccounts(savingAccountNumbers: [String], checkingAccountNumbers: [String]): TransactionResult
}

type Mutation {
    populateAccounts(savingAccounts: [SavingAccountInput], checkingAccounts: [CheckingAccountInput]): TransactionResult
    transferMoney(transactions: [TransactionInput]): TransactionResult
}
```

### TransactWriteItems - 口座の入力
<a name="transactwriteitems-populate-accounts-js"></a>

口座間で振替を行うには、テーブルに詳細を入力する必要があります。これを実行するには、GraphQL オペレーション `Mutation.populateAccounts` を使用します。

[スキーマ] セクションで、`Mutation.populateAccounts` オペレーションの横にある **[アタッチ]** をクリックします。`TransactTutorial` データソースを選択し、**[作成]** を選択します。

ここで、以下のコードを使用します。

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { savingAccounts, checkingAccounts } = ctx.args

	const savings = savingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'savingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})

	const checkings = checkingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'checkingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})
	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const { savingAccounts: sInput, checkingAccounts: cInput } = ctx.args
	const keys = ctx.result.keys
	const savingAccounts = sInput.map((_, i) => keys[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => keys[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

リゾルバーを保存し、 AWS AppSync コンソールの**クエリ**セクションに移動してアカウントを入力します。

次のミューテーションを実行します。

```
mutation populateAccounts {
  populateAccounts (
    savingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 100},
      {accountNumber: "2", username: "Amy", balance: 90},
      {accountNumber: "3", username: "Lily", balance: 80},
    ]
    checkingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 70},
      {accountNumber: "2", username: "Amy", balance: 60},
      {accountNumber: "3", username: "Lily", balance: 50},
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
  }
}
```

1 つのミューテーションで 3 つの普通預金口座と 3 つの当座預金口座に入力しました。

DynamoDB コンソールを使用して、**savingAccounts** テーブルと **checkingAccounts** テーブルの両方にデータが表示されることを確認します。

### TransactWriteItems - 振替
<a name="transactwriteitems-transfer-money-js"></a>

次のコードを使用して `transferMoney` ミューテーションにリゾルバーをアタッチします。送金ごとに、当座預金口座と普通預金口座の両方に成功修飾子が必要で、取引中の送金を追跡する必要があります。

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const transactions = ctx.args.transactions

	const savings = []
	const checkings = []
	const history = []
	transactions.forEach((t) => {
		const { savingAccountNumber, checkingAccountNumber, amount } = t
		savings.push({
			table: 'savingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: savingAccountNumber }),
			update: {
				expression: 'SET balance = balance - :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		checkings.push({
			table: 'checkingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: checkingAccountNumber }),
			update: {
				expression: 'SET balance = balance + :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		history.push({
			table: 'transactionHistory',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ transactionId: util.autoId() }),
			attributeValues: util.dynamodb.toMapValues({
				from: savingAccountNumber,
				to: checkingAccountNumber,
				amount,
			}),
		})
	})

	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings, ...history],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const tInput = ctx.args.transactions
	const tLength = tInput.length
	const keys = ctx.result.keys
	const savingAccounts = tInput.map((_, i) => keys[tLength * 0 + i])
	const checkingAccounts = tInput.map((_, i) => keys[tLength * 1 + i])
	const transactionHistory = tInput.map((_, i) => keys[tLength * 2 + i])
	return { savingAccounts, checkingAccounts, transactionHistory }
}
```

次に、 AWS AppSync コンソールの**クエリ**セクションに移動し、次のように **transferMoney** ミューテーションを実行します。

```
mutation write {
  transferMoney(
    transactions: [
      {savingAccountNumber: "1", checkingAccountNumber: "1", amount: 7.5},
      {savingAccountNumber: "2", checkingAccountNumber: "2", amount: 6.0},
      {savingAccountNumber: "3", checkingAccountNumber: "3", amount: 3.3}
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
    transactionHistory {
      transactionId
    }
  }
}
```

1 つのミューテーションで 2 つの銀行取引を送信しました。DynamoDB コンソールを使用して、**savingAccounts**、**checkingAccounts**、および **transactionHistory** の各テーブルにデータが表示されることを検証します。

### TransactGetItems - 口座の取得
<a name="transactgetitems-retrieve-accounts-js"></a>

1 つの取引リクエストで普通預金口座と当座預金口座から詳細を取得するために、スキーマで `Query.getAccounts` GraphQL オペレーションにリゾルバーをアタッチします。**[アタッチ]** を選択し、次の画面で、このチュートリアルの最初で作成したのと同じ `TransactTutorial` データソースを選択します。以下のコードを使用します。

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { savingAccountNumbers, checkingAccountNumbers } = ctx.args

	const savings = savingAccountNumbers.map((accountNumber) => {
		return { table: 'savingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	const checkings = checkingAccountNumbers.map((accountNumber) => {
		return { table: 'checkingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	return {
		version: '2018-05-29',
		operation: 'TransactGetItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}

	const { savingAccountNumbers: sInput, checkingAccountNumbers: cInput } = ctx.args
	const items = ctx.result.items
	const savingAccounts = sInput.map((_, i) => items[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => items[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

リゾルバーを保存し、AppSyncコンソールの**[クエリ]** AWS セクションに移動します。普通預金口座と当座預金口座を取得するには、次のクエリを実行します。

```
query getAccounts {
  getAccounts(
    savingAccountNumbers: ["1", "2", "3"],
    checkingAccountNumbers: ["1", "2"]
  ) {
    savingAccounts {
      accountNumber
      username
      balance
    }
    checkingAccounts {
      accountNumber
      username
      balance
    }
  }
}
```

以上で、 AWS AppSync を使用した DynamoDB のトランザクションのデモンストレーションが完了しました。