

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 在中执行 DynamoDB 事务 AWS AppSync
<a name="tutorial-dynamodb-transact-js"></a>

AWS AppSync 支持对单个区域中的一个或多个表使用 Amazon DynamoDB 事务操作。支持的操作为 `TransactGetItems` 和 `TransactWriteItems`。通过在中使用这些功能 AWS AppSync，您可以执行以下任务：
+ 在单个查询中传递键列表，并从表中返回结果
+ 在单个查询中从一个或多个表中读取记录
+ 以 all-or-nothing某种方式将事务中的记录写入一个或多个表
+ 在满足某些条件时运行事务

## Permissions
<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 获取的数据来源仅指定一个表，以使配置保持简单。因此，当在单个解析器中针对多个表执行事务操作（这是一项更高级的任务）时，您必须向该数据来源的角色授予对将与解析器进行交互的任何表的访问权限。这项操作将在上面 IAM 策略中的 **Resource (资源)** 字段中执行。针对表的事务调用的配置是在解析器代码中完成的，我们将在下面进行介绍。

## 数据来源
<a name="data-source-js"></a>

为简便起见，我们将为本教程中使用的所有解析器使用同一个数据来源。

我们具有两个名为 **savingAccounts** 和 **checkingAccounts** 的表，它们将 `accountNumber` 作为分区键，还有一个 **transactionHistory** 表，它将 `transactionId` 作为分区键。您可以使用下面的 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
    }
  }
}
```

我们在一个变更中填充了三个储蓄账户和三个支票账户。

使用 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
    }
  }
}
```

我们在一个变更中发送了三笔银行交易。使用 DynamoDB 控制台验证是否在 **savingAccounts**、**checkingAccounts** 和 **transactionHistory** 表中显示数据。

### TransactGetItems -检索账户
<a name="transactgetitems-retrieve-accounts-js"></a>

为了在单个事务请求中从储蓄和支票账户中检索详细信息，我们将一个解析器附加到架构上的 `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 }
}
```

保存解析器并导航到 AWS AppSync 控制台的 “**查询**” 部分。为了检索储蓄和支票账户，执行以下查询：

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

我们已经成功演示了使用DynamoDB事务的用法。 AWS AppSync