教程:DynamoDB 事务解析器 - Amazon AppSync
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

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

教程:DynamoDB 事务解析器

注意

我们现在主要支持 APPSYNC_JS 运行时环境及其文档。请考虑使用 APPSYNC_JS 运行时环境和此处的指南。

Amazon AppSync 支持对单个区域中的一个或多个表执行 Amazon DynamoDB 事务操作。支持的操作为 TransactGetItemsTransactWriteItems。通过在 Amazon AppSync 中使用这些功能,您可以执行如下任务:

  • 在单个查询中传递键列表,并从表中返回结果

  • 在单个查询中从一个或多个表读取记录

  • 以要么全部写入,要么全不写入的方式将事务记录写入一个或多个表

  • 在满足某些条件时执行事务

权限

与其他解析器一样,您需要在 Amazon AppSync 中创建数据源,并创建一个角色或使用现有的角色。由于事务操作需要具有 DynamoDB 表的不同权限,因此,您需要为配置的角色授予读取或写入操作权限:

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

注意:角色与 Amazon AppSync 中的数据源相关联,并对数据源调用字段上的解析器。配置为针对 DynamoDB 获取的数据源仅指定一个表,以使配置保持简单。因此,当在单个解析器中针对多个表执行事务操作(这是一项更高级的任务)时,您必须向该数据源的角色授予对将与解析器进行交互的任何表的访问权限。这项操作将在上面 IAM 策略中的 Resource (资源) 字段中执行。针对表的事务调用配置在解析器模板中完成,下面将介绍相关内容。

数据源

为简便起见,我们将为本教程中使用的所有解析器使用同一个数据源。在数据源选项卡上,创建一个新的 DynamoDB 数据源,并将其命名为 TransactTutorial。表名称可以是任何内容,因为表名称被指定为事务操作的请求映射模板的一部分。我们将提供表名称 empty

我们将有两个表,分别称为 savingAccountscheckingAccounts,两个表都使用 accountNumber 作为分区键,还有一个 transactionHistory 表,该表使用 transactionId 作为分区键。

对于本教程,具有以下内联策略的任何角色都有效。将 regionaccountId 替换为您的区域和账户 ID:

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

事务

对于本示例,上下文是一个典型的银行交易,我们将使用 TransactWriteItems 来执行以下操作:

  • 从储蓄账户转账到支票账户

  • 为每个交易生成新的交易记录

然后我们将使用 TransactGetItems 从储蓄账户和支票账户中检索详细信息。

我们按如下所示定义我们的 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 } schema { query: Query mutation: Mutation }

TransactWriteItems - 填充账户

为了在账户之间转账,我们需要用详细信息填充表格。我们将使用 GraphQL 操作 Mutation.populateAccounts 来实现此目的。

在“架构”部分中,单击 Mutation.populateAccounts 操作旁边的附加。转到“VTL 单位解析器”,然后选择相同的 TransactTutorial 数据源。

现在使用以下请求映射模板:

请求映射模板

#set($savingAccountTransactPutItems = []) #set($index = 0) #foreach($savingAccount in ${ctx.args.savingAccounts}) #set($keyMap = {}) $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($savingAccount.accountNumber))) #set($attributeValues = {}) $util.qr($attributeValues.put("username", $util.dynamodb.toString($savingAccount.username))) $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($savingAccount.balance))) #set($index = $index + 1) #set($savingAccountTransactPutItem = {"table": "savingAccounts", "operation": "PutItem", "key": $keyMap, "attributeValues": $attributeValues}) $util.qr($savingAccountTransactPutItems.add($savingAccountTransactPutItem)) #end #set($checkingAccountTransactPutItems = []) #set($index = 0) #foreach($checkingAccount in ${ctx.args.checkingAccounts}) #set($keyMap = {}) $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($checkingAccount.accountNumber))) #set($attributeValues = {}) $util.qr($attributeValues.put("username", $util.dynamodb.toString($checkingAccount.username))) $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($checkingAccount.balance))) #set($index = $index + 1) #set($checkingAccountTransactPutItem = {"table": "checkingAccounts", "operation": "PutItem", "key": $keyMap, "attributeValues": $attributeValues}) $util.qr($checkingAccountTransactPutItems.add($checkingAccountTransactPutItem)) #end #set($transactItems = []) $util.qr($transactItems.addAll($savingAccountTransactPutItems)) $util.qr($transactItems.addAll($checkingAccountTransactPutItems)) { "version" : "2018-05-29", "operation" : "TransactWriteItems", "transactItems" : $util.toJson($transactItems) }

和以下响应映射模板:

响应映射模板

#if ($ctx.error) $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons) #end #set($savingAccounts = []) #foreach($index in [0..2]) $util.qr($savingAccounts.add(${ctx.result.keys[$index]})) #end #set($checkingAccounts = []) #foreach($index in [3..5]) $util.qr($checkingAccounts.add(${ctx.result.keys[$index]})) #end #set($transactionResult = {}) $util.qr($transactionResult.put('savingAccounts', $savingAccounts)) $util.qr($transactionResult.put('checkingAccounts', $checkingAccounts)) $util.toJson($transactionResult)

保存解析器,并导航到 Amazon 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 } } }

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

使用 DynamoDB 控制台验证是否在 savingAccountscheckingAccounts 表中显示数据。

TransactWriteItems - 转账

使用以下请求映射模板将一个解析器附加到 transferMoney 变更。请注意 amountssavingAccountNumberscheckingAccountNumbers 的值相同。

#set($amounts = []) #foreach($transaction in ${ctx.args.transactions}) #set($attributeValueMap = {}) $util.qr($attributeValueMap.put(":amount", $util.dynamodb.toNumber($transaction.amount))) $util.qr($amounts.add($attributeValueMap)) #end #set($savingAccountTransactUpdateItems = []) #set($index = 0) #foreach($transaction in ${ctx.args.transactions}) #set($keyMap = {}) $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.savingAccountNumber))) #set($update = {}) $util.qr($update.put("expression", "SET balance = balance - :amount")) $util.qr($update.put("expressionValues", $amounts[$index])) #set($index = $index + 1) #set($savingAccountTransactUpdateItem = {"table": "savingAccounts", "operation": "UpdateItem", "key": $keyMap, "update": $update}) $util.qr($savingAccountTransactUpdateItems.add($savingAccountTransactUpdateItem)) #end #set($checkingAccountTransactUpdateItems = []) #set($index = 0) #foreach($transaction in ${ctx.args.transactions}) #set($keyMap = {}) $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.checkingAccountNumber))) #set($update = {}) $util.qr($update.put("expression", "SET balance = balance + :amount")) $util.qr($update.put("expressionValues", $amounts[$index])) #set($index = $index + 1) #set($checkingAccountTransactUpdateItem = {"table": "checkingAccounts", "operation": "UpdateItem", "key": $keyMap, "update": $update}) $util.qr($checkingAccountTransactUpdateItems.add($checkingAccountTransactUpdateItem)) #end #set($transactionHistoryTransactPutItems = []) #foreach($transaction in ${ctx.args.transactions}) #set($keyMap = {}) $util.qr($keyMap.put("transactionId", $util.dynamodb.toString(${utils.autoId()}))) #set($attributeValues = {}) $util.qr($attributeValues.put("from", $util.dynamodb.toString($transaction.savingAccountNumber))) $util.qr($attributeValues.put("to", $util.dynamodb.toString($transaction.checkingAccountNumber))) $util.qr($attributeValues.put("amount", $util.dynamodb.toNumber($transaction.amount))) #set($transactionHistoryTransactPutItem = {"table": "transactionHistory", "operation": "PutItem", "key": $keyMap, "attributeValues": $attributeValues}) $util.qr($transactionHistoryTransactPutItems.add($transactionHistoryTransactPutItem)) #end #set($transactItems = []) $util.qr($transactItems.addAll($savingAccountTransactUpdateItems)) $util.qr($transactItems.addAll($checkingAccountTransactUpdateItems)) $util.qr($transactItems.addAll($transactionHistoryTransactPutItems)) { "version" : "2018-05-29", "operation" : "TransactWriteItems", "transactItems" : $util.toJson($transactItems) }

我们将在一个 TransactWriteItems 操作中进行 3 笔银行交易。使用以下响应映射模板

#if ($ctx.error) $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons) #end #set($savingAccounts = []) #foreach($index in [0..2]) $util.qr($savingAccounts.add(${ctx.result.keys[$index]})) #end #set($checkingAccounts = []) #foreach($index in [3..5]) $util.qr($checkingAccounts.add(${ctx.result.keys[$index]})) #end #set($transactionHistory = []) #foreach($index in [6..8]) $util.qr($transactionHistory.add(${ctx.result.keys[$index]})) #end #set($transactionResult = {}) $util.qr($transactionResult.put('savingAccounts', $savingAccounts)) $util.qr($transactionResult.put('checkingAccounts', $checkingAccounts)) $util.qr($transactionResult.put('transactionHistory', $transactionHistory)) $util.toJson($transactionResult)

现在,导航到 Amazon 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 } } }

我们在一个变更中发送了 2 笔银行交易。使用 DynamoDB 控制台验证是否在 savingAccountscheckingAccountstransactionHistory 表中显示数据。

TransactGetItems - 检索账户

为了在单个交易请求中从储蓄账户和支票账户检索详细信息,我们需要将解析器附加到架构上的 Query.getAccounts GraphQL 操作。选择附加,转到“VTL 单位解析器”,然后在下一个屏幕上选择在教程开始时创建的相同 TransactTutorial 数据源。按如下所示配置模板:

请求映射模板

#set($savingAccountsTransactGets = []) #foreach($savingAccountNumber in ${ctx.args.savingAccountNumbers}) #set($savingAccountKey = {}) $util.qr($savingAccountKey.put("accountNumber", $util.dynamodb.toString($savingAccountNumber))) #set($savingAccountTransactGet = {"table": "savingAccounts", "key": $savingAccountKey}) $util.qr($savingAccountsTransactGets.add($savingAccountTransactGet)) #end #set($checkingAccountsTransactGets = []) #foreach($checkingAccountNumber in ${ctx.args.checkingAccountNumbers}) #set($checkingAccountKey = {}) $util.qr($checkingAccountKey.put("accountNumber", $util.dynamodb.toString($checkingAccountNumber))) #set($checkingAccountTransactGet = {"table": "checkingAccounts", "key": $checkingAccountKey}) $util.qr($checkingAccountsTransactGets.add($checkingAccountTransactGet)) #end #set($transactItems = []) $util.qr($transactItems.addAll($savingAccountsTransactGets)) $util.qr($transactItems.addAll($checkingAccountsTransactGets)) { "version" : "2018-05-29", "operation" : "TransactGetItems", "transactItems" : $util.toJson($transactItems) }

响应映射模板

#if ($ctx.error) $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons) #end #set($savingAccounts = []) #foreach($index in [0..2]) $util.qr($savingAccounts.add(${ctx.result.items[$index]})) #end #set($checkingAccounts = []) #foreach($index in [3..4]) $util.qr($checkingAccounts.add($ctx.result.items[$index])) #end #set($transactionResult = {}) $util.qr($transactionResult.put('savingAccounts', $savingAccounts)) $util.qr($transactionResult.put('checkingAccounts', $checkingAccounts)) $util.toJson($transactionResult)

保存解析器,并导航到 Amazon AppSync 控制台的查询部分。为了检索储蓄账户和支票账户,请执行以下查询:

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

我们已成功说明了如何通过 Amazon AppSync 使用 DynamoDB 事务。