

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 在 AWS AppSync 中執行 DynamoDB 交易
<a name="tutorial-dynamodb-transact-js"></a>

AWS AppSync 支援在單一區域中的一或多個資料表中使用 Amazon DynamoDB 交易操作。支援的操作包括 `TransactGetItems` 和 `TransactWriteItems`。透過使用這些 in AWS AppSync 功能，您可以執行下列任務：
+ 在單一查詢中傳遞金鑰清單，並從資料表傳回結果
+ 在單一查詢中從一或多個資料表讀取記錄
+ 以all-or-nothing方式將交易中的記錄寫入一或多個資料表
+ 滿足某些條件時執行交易

## 許可
<a name="permissions-js"></a>

與其他解析程式一樣，您需要建立資料來源 in 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/*"
            ]
        }
    ]
}
```

------

**注意**  
角色會繫結至 in AWS AppSync 中的資料來源，而 欄位上的解析程式會針對資料來源叫用。設定為針對 DynamoDB 擷取的資料來源僅指定一個資料表，以保持組態簡單。因此，在單一解析程式中針對多個資料表執行交易操作時 (這是一項更為進階的工作)，您必須授予權限給該資料來源上的角色，以存取解析程式將會與其互動的所有資料表。這會在上述 IAM 政策的 **Resource (資源)** 欄位中完成。針對資料表的交易呼叫組態是在解析程式程式碼中完成，如下所述。

## 資料來源
<a name="data-source-js"></a>

為簡單起見，我們將針對本教學課程中使用的所有解析程式，使用相同的資料來源。

我們將有兩個名為 **savingAccounts** 和 **checkingAccounts**的資料表，兩個資料表都以 `accountNumber`做為分割區索引鍵，而以 `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
    }
  }
}
```

我們在一個變動中填入三個儲存帳戶和三個檢查帳戶。

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

我們已成功使用 AWS AppSync 示範 DynamoDB 交易的使用方式。