

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

# 適用於 AWS AppSync 的 JavaScript 解析程式教學課程
<a name="tutorials-js"></a>

 AWS AppSync 會使用資料來源和解析程式來翻譯 GraphQL 請求，並從 AWS 資源擷取資訊。 AWS AppSync 支援使用特定資料來源類型的自動佈建和連線。 AWS AppSync 也支援 Amazon DynamoDB AWS Lambda、關聯式資料庫 (Amazon Aurora Serverless)、Amazon OpenSearch Service 和 HTTP 端點做為資料來源。您可以將 GraphQL API 與現有的 AWS 資源搭配使用，或從頭建置資料來源和解析程式。以下章節旨在以教學的形式闡明一些較常見的 GraphQL 使用案例。

**Topics**
+ [使用 DynamoDB JavaScript 解析程式建立簡單的貼文應用程式](tutorial-dynamodb-resolvers-js.md)
+ [使用 AWS Lambda 解析程式](tutorial-lambda-resolvers-js.md)
+ [使用本機解析程式](tutorial-local-resolvers-js.md)
+ [結合 GraphQL 解析程式](tutorial-combining-graphql-resolvers-js.md)
+ [使用 OpenSearch Service 解析程式](tutorial-elasticsearch-resolvers-js.md)
+ [執行 DynamoDB 交易](tutorial-dynamodb-transact-js.md)
+ [使用 DynamoDB 批次操作](tutorial-dynamodb-batch-js.md)
+ [使用 HTTP 解析程式](tutorial-http-resolvers-js.md)
+ [搭配資料 API 使用 Aurora PostgreSQL](aurora-serverless-tutorial-js.md)

# 使用 DynamoDB JavaScript 解析程式建立簡單的貼文應用程式
<a name="tutorial-dynamodb-resolvers-js"></a>

在本教學課程中，您將 Amazon DynamoDB 資料表匯入 AWS AppSync ，並使用 JavaScript 管道解析程式來連接它們，以建置功能完整的 GraphQL API，供您在自己的應用程式中使用。

您將使用 AWS AppSync 主控台來佈建 Amazon DynamoDB 資源、建立解析程式，並將它們連接到您的資料來源。您也可以透過 GraphQL 陳述式讀取和寫入 Amazon DynamoDB 資料庫，並訂閱即時資料。

您必須完成特定步驟，才能將 GraphQL 陳述式翻譯為 Amazon DynamoDB 操作，以及將回應翻譯回 GraphQL。本教學課程概述透過幾個真實世界案例和資料存取模式的組態程序。

## 建立 GraphQL API
<a name="create-graphql-api"></a>

**在 中建立 GraphQL API AWS AppSync**

1. 開啟 AppSync 主控台，然後選擇**建立 API**。

1. 選取**從頭開始設計**，然後選擇**下一步**。

1. 為您的 API 命名`PostTutorialAPI`，然後選擇**下一步**。跳到檢閱頁面，同時將其餘選項設定為其預設值，然後選擇 `Create`。

 AWS AppSync 主控台會為您建立新的 GraphQL API。透過停用，它會使用 API 金鑰身分驗證模式。您可以使用主控台來設定其他 GraphQL API 和對其執行查詢，以進行此教學的其他部分。

## 定義基本文章 API
<a name="define-post-api"></a>

現在您已擁有 GraphQL API，您可以設定基本結構描述，以允許基本建立、擷取和刪除文章資料。

**將資料新增至您的結構描述**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 我們將建立結構描述，定義 `Post`類型和 操作`addPost`來新增和取得`Post`物件。在**結構描述**窗格中，使用下列程式碼取代內容：

   ```
   schema {
       query: Query
       mutation: Mutation
   }
   
   type Query {
       getPost(id: ID): Post
   }
   
   type Mutation {
       addPost(
           id: ID!
           author: String!
           title: String!
           content: String!
           url: String!
       ): Post!
   }
   
   type Post {
       id: ID!
       author: String
       title: String
       content: String
       url: String
       ups: Int!
       downs: Int!
       version: Int!
   }
   ```

1. 選擇 **Save Schema (儲存結構描述)**。

## 設定 Amazon DynamoDB 資料表
<a name="configure-dynamodb"></a>

 AWS AppSync 主控台可協助佈建所需的 AWS 資源，以將您自己的資源存放在 Amazon DynamoDB 資料表中。在此步驟中，您將建立 Amazon DynamoDB 資料表來存放您的文章。您也將設定[次要索引](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html)，以供我們稍後使用。

**建立 Amazon DynamoDB 資料表**

1. 在**結構描述**頁面上，選擇**建立資源**。

1. 選擇**使用現有類型**，然後選擇`Post`類型。

1. 在**其他索引**區段中，選擇**新增索引**。

1. 為索引命名 `author-index`。

1. 將 `Primary key` 設定為 `author`，將 `Sort`金鑰設定為 `None`。

1. 停用 **自動產生 GraphQL**。在此範例中，我們將自行建立解析程式。

1. 選擇**建立**。

您現在有名為 的新資料來源`PostTable`，您可以透過前往側邊標籤中的**資料來源**來查看。您將使用此資料來源將查詢和變動連結至 Amazon DynamoDB 資料表。

## 設定 addPost 解析程式 (Amazon DynamoDB PutItem)
<a name="configure-addpost"></a>

現在 AWS AppSync ，您知道 Amazon DynamoDB 資料表，您可以透過定義解析程式將其連結至個別查詢和變動。您建立的第一個解析程式是使用 JavaScript 的`addPost`管道解析程式，可讓您在 Amazon DynamoDB 資料表中建立文章。管道解析程式具有下列元件：
+ 在 GraphQL 結構描述中要附加解析程式的位置。在這種情況下，您會對 `createPost` 類型上的 `Mutation` 欄位設定解析程式。當發起人呼叫變動 時，將會叫用此解析程式`{ addPost(...){...} }`。
+ 用於此解析程式的資料來源。在此情況下，您想要使用先前定義的 DynamoDB 資料來源，因此您可以將項目新增至 `post-table-for-tutorial` DynamoDB 資料表。
+ 請求處理常式。請求處理常式是一種函數，可處理來自發起人的傳入請求，並將其轉換為指示 AWS AppSync ，讓 對 DynamoDB 執行。
+ 回應處理常式。回應處理常式的任務是處理來自 DynamoDB 的回應，並將其翻譯回 GraphQL 預期的內容。如果 DynamoDB 中的資料形狀與 GraphQL 中的 `Post` 類型不同，此功能會很有用，但如果他們的形狀一樣，您只需傳遞資料。

**設定您的解析程式**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 在**解析程式**窗格中，尋找 `Mutation`類型下的 `addPost` 欄位，然後選擇**連接**。

1. 選擇您的資料來源，然後選擇**建立**。

1. 在您的程式碼編輯器中，使用此程式碼片段取代程式碼：

   ```
   import { util } from '@aws-appsync/utils'
   import * as ddb from '@aws-appsync/utils/dynamodb'
   
   export function request(ctx) {
   	const item = { ...ctx.arguments, ups: 1, downs: 0, version: 1 }
   	const key = { id: ctx.args.id ?? util.autoId() }
   	return ddb.put({ key, item })
   }
   
   export function response(ctx) {
   	return ctx.result
   }
   ```

1. 選擇**儲存**。

**注意**  
在此程式碼中，您可以使用 DynamoDB 模組 utils，讓您輕鬆地建立 DynamoDB 請求。

AWS AppSync 隨附名為 的自動 ID 產生公用程式`util.autoId()`，用於產生新文章的 ID。如果您未指定 ID，則公用程式會自動為您產生 ID。

```
const key = { id: ctx.args.id ?? util.autoId() }
```

如需 JavaScript 可用公用程式的詳細資訊，請參閱[解析程式和函數的 JavaScript 執行期功能](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html)。

### 呼叫 API 以新增文章
<a name="call-api-addpost"></a>

現在已設定解析程式， AWS AppSync 可以將傳入`addPost`的變動轉譯為 Amazon DynamoDB `PutItem`操作。您現在可以執行變動以將項目放置到資料表中。

**執行 操作**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列變動：

   ```
   mutation addPost {
     addPost(
       id: 123,
       author: "AUTHORNAME"
       title: "Our first post!"
       content: "This is our first post."
       url: "https://aws.amazon.com/appsync/"
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `addPost`。新建立的文章結果應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "addPost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our first post!",
         "content": "This is our first post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

下列說明顯示發生的情況：

1. AWS AppSync 收到`addPost`變動請求。

1. AWS AppSync 會執行解析程式的請求處理常式。`ddb.put` 函數會建立如下所示的`PutItem`請求：

   ```
   {
     operation: 'PutItem',
     key: { id: { S: '123' } },
     attributeValues: {
       downs: { N: 0 },
       author: { S: 'AUTHORNAME' },
       ups: { N: 1 },
       title: { S: 'Our first post!' },
       version: { N: 1 },
       content: { S: 'This is our first post.' },
       url: { S: 'https://aws.amazon.com/appsync/' }
     }
   }
   ```

1. AWS AppSync 使用此值來產生和執行 Amazon DynamoDB `PutItem`請求。

1. AWS AppSync 取得`PutItem`請求的結果，並將其轉換回 GraphQL 類型。

   ```
   {
       "id" : "123",
       "author": "AUTHORNAME",
       "title": "Our first post!",
       "content": "This is our first post.",
       "url": "https://aws.amazon.com/appsync/",
       "ups" : 1,
       "downs" : 0,
       "version" : 1
   }
   ```

1. 回應處理常式會立即傳回結果 (`return ctx.result`)。

1. 最終結果會顯示在 GraphQL 回應中。

## 設定 getPost 解析程式 (Amazon DynamoDB GetItem)
<a name="configure-getpost"></a>

現在，您可以將資料新增至 Amazon DynamoDB 資料表，您需要設定`getPost`查詢，以便從資料表擷取該資料。若要執行此作業，您可以設定另一個解析程式。

**新增您的解析程式**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 在右側**解析程式**窗格中，尋找 `Query`類型的 `getPost` 欄位，然後選擇**連接**。

1. 選擇您的資料來源，然後選擇**建立**。

1. 在程式碼編輯器中，使用此程式碼片段取代程式碼：

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb'
   	
   export function request(ctx) {
   	return ddb.get({ key: { id: ctx.args.id } })
   }
   
   export const response = (ctx) => ctx.result
   ```

1. 儲存您的解析程式。

**注意**  
在此解析程式中，我們使用回應處理常式的箭頭函數表達式。

### 呼叫 API 以取得文章
<a name="call-api-getpost"></a>

現在已設定解析程式， AWS AppSync 知道如何將傳入的`getPost`查詢轉譯為 Amazon DynamoDB `GetItem`操作。您現在可以執行查詢，擷取您稍早建立的貼文。

**執行查詢**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列程式碼，並使用您在建立文章後複製的 ID：

   ```
   query getPost {
     getPost(id: "123") {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `getPost`。新建立的文章結果應該會出現在**查詢**窗格右側的**結果**窗格中。

1. 從 Amazon DynamoDB 擷取的文章應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "getPost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our first post!",
         "content": "This is our first post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

或者，請採用下列範例：

```
query getPost {
  getPost(id: "123") {
    id
    author
    title
  }
}
```

如果您的`getPost`查詢只需要 `id`、 `author`和 `title`，您可以將請求函數變更為使用投影表達式，以僅指定您想要從 DynamoDB 資料表傳輸的屬性，以避免從 DynamoDB 傳輸不必要的資料 AWS AppSync。例如，請求函數可能看起來像下面的程式碼片段：

```
import * as ddb from '@aws-appsync/utils/dynamodb'

export function request(ctx) {
	return ddb.get({
		key: { id: ctx.args.id },
		projection: ['author', 'id', 'title'],
	})
}

export const response = (ctx) => ctx.result
```

您也可以搭配 使用 [selectionSetList](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference-js.html#aws-appsync-resolver-context-reference-info-js) `getPost` 來代表 `expression`：

```
import * as ddb from '@aws-appsync/utils/dynamodb'

export function request(ctx) {
	const projection = ctx.info.selectionSetList.map((field) => field.replace('/', '.'))
	return ddb.get({ key: { id: ctx.args.id }, projection })
}

export const response = (ctx) => ctx.result
```

## 建立 updatePost 變動 (Amazon DynamoDB UpdateItem)
<a name="configure-updatepost"></a>

到目前為止，您可以在 Amazon DynamoDB 中建立和擷取`Post`物件。接著，您將設定新的變動來更新物件。相較於需要指定所有欄位的`addPost`變動，此變動可讓您只指定要變更的欄位。它還引入了一個新的`expectedVersion`引數，允許您指定要修改的版本。您將設定條件，以確保您正在修改物件的最新版本。您將使用 `UpdateItem` Amazon DynamoDB https：//operation.sc

**更新解析程式**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 修改 **Schema (結構描述)** 窗格中的 `Mutation` 類型來新增 `updatePost` 變動，如下所示：

   ```
   type Mutation {
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       
       addPost(
           id: ID
           author: String!
           title: String!
           content: String!
           url: String!
       ): Post!
   }
   ```

1. 選擇 **Save Schema (儲存結構描述)**。

1. 在右側的**解析程式**窗格中，尋找`Mutation`類型上新建立`updatePost`的欄位，然後選擇**連接**。使用以下程式碼片段建立新的解析程式：

   ```
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { id, expectedVersion, ...rest } = ctx.args;
     const values = Object.entries(rest).reduce((obj, [key, value]) => {
       obj[key] = value ?? ddb.operations.remove();
       return obj;
     }, {});
   
     return ddb.update({
       key: { id },
       condition: { version: { eq: expectedVersion } },
       update: { ...values, version: ddb.operations.increment(1) },
     });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   ```

1. 儲存您所做的任何變更。

此解析程式使用 `ddb.update`來建立 Amazon DynamoDB `UpdateItem`請求。您只是要求 Amazon DynamoDB 更新特定屬性，而不是撰寫整個項目。這是使用 Amazon DynamoDB 更新表達式完成。

`ddb.update` 函數會採用金鑰和更新物件做為引數。然後，檢查傳入引數的值。將值設定為 時`null`，請使用 DynamoDB `remove`操作來表示應該從 DynamoDB 項目中移除該值。

也有新的`condition`區段。條件表達式可讓您在執行操作之前，根據 Amazon DynamoDB 中已存在的物件狀態，告知 AWS AppSync 和 Amazon DynamoDB 請求是否應成功。在此情況下，只有當 Amazon DynamoDB 中目前項目`version`的欄位完全符合`expectedVersion`引數時，您才希望`UpdateItem`請求成功。當項目更新時，我們希望增加 的值`version`。這很容易使用 操作函數 執行`increment`。

如需條件表達式的詳細資訊，請參閱[條件表達](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-condition-expressions)式文件。

如需`UpdateItem`請求的詳細資訊，請參閱 [UpdateItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-updateitem) 文件和 [DynamoDB 模組](https://docs.aws.amazon.com/appsync/latest/devguide/built-in-modules-js.html)文件。

如需有關如何撰寫更新表達式的詳細資訊，請參閱 [DynamoDB UpdateExpressions 文件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)。

### 呼叫 API 以更新文章
<a name="call-api-updatepost"></a>

讓我們嘗試使用新的解析程式更新`Post`物件。

**更新物件**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列變動。您也需要將`id`引數更新為您先前記下的值：

   ```
   mutation updatePost {
     updatePost(
       id:123
       title: "An empty story"
       content: null
       expectedVersion: 1
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `updatePost`。

1. Amazon DynamoDB 中更新的文章應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "updatePost": {
         "id": "123",
         "author": "A new author",
         "title": "An empty story",
         "content": null,
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 2
       }
     }
   }
   ```

在此請求中，您要求 AWS AppSync 和 Amazon DynamoDB 僅更新 `title`和 `content` 欄位。所有其他欄位都是單獨保留的 （非遞增`version`欄位）。您可以將 `title` 屬性設定為新值，並從文章中移除 `content` 屬性。`author`、`url`、`ups` 和 `downs` 欄位維持原狀。嘗試再次執行變動請求，同時完全離開請求。您應該會看到類似以下的回應：

```
{
  "data": {
    "updatePost": null
  },
  "errors": [
    {
      "path": [
        "updatePost"
      ],
      "data": null,
      "errorType": "DynamoDB:ConditionalCheckFailedException",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 1RR3QN5F35CS8IV5VR4OQO9NNBVV4KQNSO5AEMVJF66Q9ASUAAJG)"
    }
  ]
}
```

請求失敗，因為條件表達式評估為 `false`：

1. 第一次執行請求時，Amazon DynamoDB 中文章的 `version` 欄位值為 `1`，符合 `expectedVersion`引數。請求成功，這表示 `version` 欄位已在 Amazon DynamoDB 中遞增至 `2`。

1. 第二次執行請求時，Amazon DynamoDB 中文章的 `version` 欄位值為 `2`，這不符合引`expectedVersion`數。

此模式通常稱為*樂觀鎖定*。

## 建立投票變動 (Amazon DynamoDB UpdateItem)
<a name="configure-vote-mutations"></a>

`Post` 類型包含 `ups`和 `downs` 欄位，以啟用上標和下標的記錄。不過，目前 API 不會讓我們對它們執行任何操作。讓我們新增變動，讓我們向上和向下移動文章。

**新增您的變動**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 在**結構描述**窗格中，修改 `Mutation` 類型並新增`DIRECTION`列舉以新增新的投票變動：

   ```
   type Mutation {
       vote(id: ID!, direction: DIRECTION!): Post
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       addPost(
           id: ID,
           author: String!,
           title: String!,
           content: String!,
           url: String!
       ): Post!
   }
   
   enum DIRECTION {
     UP
     DOWN
   }
   ```

1. 選擇 **Save Schema (儲存結構描述)**。

1. 在右側的**解析程式**窗格中，尋找 `Mutation`類型上新建立`vote`的欄位，然後選擇**連接**。使用下列程式碼片段建立並取代程式碼，以建立新的解析程式：

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const field = ctx.args.direction === 'UP' ? 'ups' : 'downs';
     return ddb.update({
       key: { id: ctx.args.id },
       update: {
         [field]: ddb.operations.increment(1),
         version: ddb.operations.increment(1),
       },
     });
   }
   
   export const response = (ctx) => ctx.result;
   ```

1. 儲存您所做的任何變更。

### 呼叫 API 以上移或下移文章
<a name="call-api-vote"></a>

現在已設定新的解析程式， AWS AppSync 知道如何將傳入`upvotePost`或`downvote`變動轉譯為 Amazon DynamoDB `UpdateItem`操作。您現在可以執行變動，對您之前建立的文章表達贊同或不贊同。

**執行您的變動**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列變動。您也需要將`id`引數更新為您先前記下的值：

   ```
   mutation votePost {
     vote(id:123, direction: UP) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `votePost`。

1. Amazon DynamoDB 中更新的文章應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "vote": {
         "id": "123",
         "author": "A new author",
         "title": "An empty story",
         "content": null,
         "url": "https://aws.amazon.com/appsync/",
         "ups": 6,
         "downs": 0,
         "version": 4
       }
     }
   }
   ```

1. 選擇再**執行**幾次。每次執行查詢`1`時，您應該會看到 `ups`和 `version` 欄位遞增。

1. 變更查詢以使用不同的 呼叫它`DIRECTION`。

   ```
   mutation votePost {
     vote(id:123, direction: DOWN) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `votePost`。

   這次，您應該會看到 `downs`和 `version` 欄位`1`在您每次執行查詢時遞增。

## 設定 deletePost 解析程式 (Amazon DynamoDB DeleteItem)
<a name="configure-deletepost"></a>

接著，您會想要建立變動來刪除文章。您將使用 Amazon DynamoDB `DeleteItem` 操作來執行此操作。

**新增您的變動**

1. 在您的結構描述中，選擇**結構描述**索引標籤。

1. 在**結構描述**窗格中，修改 `Mutation`類型以新增新的`deletePost`變動：

   ```
   type Mutation {
       deletePost(id: ID!, expectedVersion: Int): Post
       vote(id: ID!, direction: DIRECTION!): Post
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       addPost(
           id: ID
           author: String!,
           title: String!,
           content: String!,
           url: String!
       ): Post!
   }
   ```

1. 這次，您將 `expectedVersion` 欄位設為選用。接著，選擇**儲存結構描述**。

1. 在右側的**解析程式**窗格中，尋找`Mutation`類型中新建立`delete`的欄位，然後選擇**連接**。使用下列程式碼建立新的解析程式：

   ```
   import { util } from '@aws-appsync/utils'
   
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     let condition = null;
     if (ctx.args.expectedVersion) {
       condition = {
         or: [
           { id: { attributeExists: false } },
           { version: { eq: ctx.args.expectedVersion } },
         ],
       };
     }
     return ddb.remove({ key: { id: ctx.args.id }, condition });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   }
   ```
**注意**  
`expectedVersion` 引數是選用的引數。如果發起人在請求中設定`expectedVersion`引數，請求處理常式會新增條件，只有在項目已刪除或 Amazon DynamoDB 中的文章`version`屬性完全符合 時，才會允許`DeleteItem`請求成功`expectedVersion`。如果省略，將不會在 `DeleteItem` 要求中指定條件表達式。無論值為 `version`或項目是否存在於 Amazon DynamoDB 中，它都會成功。  
即使您刪除項目，您也可以傳回已刪除的項目，如果該項目尚未刪除。

如需`DeleteItem`請求的詳細資訊，請參閱 [DeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-deleteitem) 文件。

### 呼叫 API 以刪除文章
<a name="call-api-delete"></a>

現在已設定解析程式， AWS AppSync 知道如何將傳入`delete`的變動轉譯為 Amazon DynamoDB `DeleteItem`操作。您現在可以執行變動以在資料表中刪除項目。

**執行您的變動**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列變動。您也需要將`id`引數更新為您先前記下的值：

   ```
   mutation deletePost {
     deletePost(id:123) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `deletePost`。

1. 文章會從 Amazon DynamoDB 刪除。請注意， 會 AWS AppSync 傳回從 Amazon DynamoDB 刪除的項目值，該值應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "deletePost": {
         "id": "123",
         "author": "A new author",
         "title": "An empty story",
         "content": null,
         "url": "https://aws.amazon.com/appsync/",
         "ups": 6,
         "downs": 4,
         "version": 12
       }
     }
   }
   ```

1. 只有在此呼叫`deletePost`是實際從 Amazon DynamoDB 刪除的值時，才會傳回此值。再次選擇**執行**。

1. 呼叫仍然成功，但不會傳回任何值：

   ```
   {
     "data": {
       "deletePost": null
     }
   }
   ```

1. 現在，讓我們嘗試刪除文章，但這次指定 `expectedValue`。首先，您需要建立新的文章，因為您剛刪除了到目前為止一直使用的文章。

1. 在**查詢**窗格中，新增下列變動：

   ```
   mutation addPost {
     addPost(
       id:123
       author: "AUTHORNAME"
       title: "Our second post!"
       content: "A new post."
       url: "https://aws.amazon.com/appsync/"
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `addPost`。

1. 新建立的文章結果應該會出現在**查詢**窗格右側的**結果**窗格中。記錄新建立物件`id`的 ，因為您只需要一點時間。其看起來與下列類似：

   ```
   {
     "data": {
       "addPost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our second post!",
         "content": "A new post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

1. 現在，讓我們嘗試刪除具有 **expectedVersion** 非法值的文章。在**查詢**窗格中，新增下列變動。您也需要將`id`引數更新為您先前記下的值：

   ```
   mutation deletePost {
     deletePost(
       id:123
       expectedVersion: 9999
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `deletePost`。會傳回下列結果：

   ```
   {
     "data": {
       "deletePost": null
     },
     "errors": [
       {
         "path": [
           "deletePost"
         ],
         "data": null,
         "errorType": "DynamoDB:ConditionalCheckFailedException",
         "errorInfo": null,
         "locations": [
           {
             "line": 2,
             "column": 3,
             "sourceName": null
           }
         ],
         "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 7083O037M1FTFRK038A4CI9H43VV4KQNSO5AEMVJF66Q9ASUAAJG)"
       }
     ]
   }
   ```

1. 請求失敗，因為條件表達式評估為 `false`。Amazon DynamoDB 中文章`version`的值與引數中`expectedValue`指定的 不相符。會在 GraphQL 回應 `data` 區段中 `errors` 欄位傳回物件的目前值。重試要求，但更正 `expectedVersion`：

   ```
   mutation deletePost {
     deletePost(
       id:123
       expectedVersion: 1
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `deletePost`。

   這次請求成功，並傳回從 Amazon DynamoDB 刪除的值：

   ```
   {
     "data": {
       "deletePost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our second post!",
         "content": "A new post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

1. 再次選擇**執行**。呼叫仍然成功，但這次不會傳回任何值，因為文章已在 Amazon DynamoDB 中刪除。

   ```
   { "data": { "deletePost": null } }
   ```

## 設定 allPost 解析程式 (Amazon DynamoDB Scan)
<a name="configure-allpost"></a>

到目前為止，只有在您知道要查看的每個文章`id`的 時，API 才有用。讓我們新增新的解析程式，傳回資料表中的所有文章。

**新增您的變動**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 修改 **Schema (結構描述)** 窗格中的 `Query` 類型以新增 `allPost` 查詢，如下所示：

   ```
   type Query {
       allPost(limit: Int, nextToken: String): PaginatedPosts!
       getPost(id: ID): Post
   }
   ```

1. 新增 `PaginationPosts` 類型：

   ```
   type PaginatedPosts {
       posts: [Post!]!
       nextToken: String
   }
   ```

1. 選擇 **Save Schema (儲存結構描述)**。

1. 在右側的**解析程式**窗格中，尋找 `Query`類型中新建立`allPost`的欄位，然後選擇**連接**。使用下列程式碼建立新的解析程式：

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken } = ctx.arguments;
     return ddb.scan({ limit, nextToken });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

   此解析程式的請求處理常式預期有兩個選用引數：
   + `limit` - 指定在單一呼叫中傳回的項目數量上限。
   + `nextToken` - 用來擷取下一組結果 （我們會顯示 的值稍後`nextToken`來自哪裡）。

1. 儲存對解析程式所做的任何變更。

如需`Scan`請求的詳細資訊，請參閱[掃描](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-scan)參考文件。

### 呼叫 API 以掃描所有文章
<a name="call-api-scan"></a>

現在已設定解析程式， AWS AppSync 知道如何將傳入的`allPost`查詢轉譯為 Amazon DynamoDB `Scan`操作。您現在可以掃描資料表來擷取所有文章。在您可以嘗試之前，您必須將一些資料填入資料表，因為您已刪除目前為止所使用的項目。

**新增和查詢資料 **

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列變動：

   ```
   mutation addPost {
     post1: addPost(id:1 author: "AUTHORNAME" title: "A series of posts, Volume 1" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post2: addPost(id:2 author: "AUTHORNAME" title: "A series of posts, Volume 2" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post3: addPost(id:3 author: "AUTHORNAME" title: "A series of posts, Volume 3" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post4: addPost(id:4 author: "AUTHORNAME" title: "A series of posts, Volume 4" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post5: addPost(id:5 author: "AUTHORNAME" title: "A series of posts, Volume 5" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post6: addPost(id:6 author: "AUTHORNAME" title: "A series of posts, Volume 6" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post7: addPost(id:7 author: "AUTHORNAME" title: "A series of posts, Volume 7" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post8: addPost(id:8 author: "AUTHORNAME" title: "A series of posts, Volume 8" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post9: addPost(id:9 author: "AUTHORNAME" title: "A series of posts, Volume 9" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
   }
   ```

1. 選擇**執行** （橘色播放按鈕）。

1. 現在，讓我們來掃描資料表，一次會傳回五個結果。在**查詢**窗格中，新增下列查詢：

   ```
   query allPost {
     allPost(limit: 5) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPost`。

   前五篇文章應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "allPost": {
         "posts": [
           {
             "id": "5",
             "title": "A series of posts, Volume 5"
           },
           {
             "id": "1",
             "title": "A series of posts, Volume 1"
           },
           {
             "id": "6",
             "title": "A series of posts, Volume 6"
           },
           {
             "id": "9",
             "title": "A series of posts, Volume 9"
           },
           {
             "id": "7",
             "title": "A series of posts, Volume 7"
           }
         ],
         "nextToken": "<token>"
       }
     }
   }
   ```

1. 您收到五個結果`nextToken`，以及可用來取得下一組結果的 。更新 `allPost` 查詢以包括來自之前結果組的 `nextToken`：

   ```
   query allPost {
     allPost(
       limit: 5
       nextToken: "<token>"
     ) {
       posts {
         id
         author
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPost`。

   其餘四篇文章應該會出現在**查詢**窗格右側的**結果**窗格中。這組結果`nextToken`中沒有 ，因為您已翻閱全部九篇文章，沒有剩餘的文章。其看起來與下列類似：

   ```
   {
     "data": {
       "allPost": {
         "posts": [
           {
             "id": "2",
             "title": "A series of posts, Volume 2"
           },
           {
             "id": "3",
             "title": "A series of posts, Volume 3"
           },
           {
             "id": "4",
             "title": "A series of posts, Volume 4"
           },
           {
             "id": "8",
             "title": "A series of posts, Volume 8"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

## 設定 allPostsByAuthor 解析程式 (Amazon DynamoDB 查詢）
<a name="configure-query"></a>

除了掃描 Amazon DynamoDB 的所有文章之外，您也可以查詢 Amazon DynamoDB 以擷取特定作者建立的文章。您先前建立的 Amazon DynamoDB 資料表已有`GlobalSecondaryIndex`稱為 的 `author-index`，您可以搭配 Amazon DynamoDB `Query`操作使用，以擷取特定作者建立的所有文章。

**新增查詢**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 修改 **Schema (結構描述)** 窗格中的 `Query` 類型以新增 `allPostsByAuthor` 查詢，如下所示：

   ```
   type Query {
       allPostsByAuthor(author: String!, limit: Int, nextToken: String): PaginatedPosts!
       allPost(limit: Int, nextToken: String): PaginatedPosts!
       getPost(id: ID): Post
   }
   ```

   請注意，這使用與`allPost`查詢相同的`PaginatedPosts`類型。

1. 選擇 **Save Schema (儲存結構描述)**。

1. 在右側的**解析程式**窗格中，尋找 `Query`類型上新建立`allPostsByAuthor`的欄位，然後選擇**連接**。使用以下程式碼片段建立解析程式：

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken, author } = ctx.arguments;
     return ddb.query({
       index: 'author-index',
       query: { author: { eq: author } },
       limit,
       nextToken,
     });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

   如同`allPost`解析程式，此解析程式有兩個選用引數：
   + `limit` - 指定在單一呼叫中傳回的項目數量上限。
   + `nextToken` - 擷取下一組結果 ( 的值`nextToken`可從先前的呼叫取得）。

1. 儲存對解析程式所做的任何變更。

如需`Query`請求的詳細資訊，請參閱[查詢](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-query)參考文件。

### 呼叫 API 以依作者查詢所有文章
<a name="call-api-query"></a>

現在已設定解析程式， AWS AppSync 知道如何針對`author-index`索引將傳入`allPostsByAuthor`的變動轉換為 DynamoDB `Query`操作。您現在可以查詢資料表以擷取特定作者的所有文章。

不過，在此之前，讓我們將更多文章填入資料表，因為到目前為止，每篇文章都有相同的作者。

**新增資料和查詢**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列變動：

   ```
   mutation addPost {
     post1: addPost(id:10 author: "Nadia" title: "The cutest dog in the world" content: "So cute. So very, very cute." url: "https://aws.amazon.com/appsync/" ) { author, title }
     post2: addPost(id:11 author: "Nadia" title: "Did you know...?" content: "AppSync works offline?" url: "https://aws.amazon.com/appsync/" ) { author, title }
     post3: addPost(id:12 author: "Steve" title: "I like GraphQL" content: "It's great" url: "https://aws.amazon.com/appsync/" ) { author, title }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `addPost`。

1. 現在，讓我們查詢資料表，傳回 `Nadia` 撰寫的所有文章。在**查詢**窗格中，新增下列查詢：

   ```
   query allPostsByAuthor {
     allPostsByAuthor(author: "Nadia") {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPostsByAuthor`。撰寫的所有文章`Nadia`都應出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "10",
             "title": "The cutest dog in the world"
           },
           {
             "id": "11",
             "title": "Did you know...?"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

1. 分頁適用於 `Query`，如同它適用於 `Scan`。例如，讓我們查詢 `AUTHORNAME` 的所有文章，一次取得五篇。

1. 在**查詢**窗格中，新增下列查詢：

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "AUTHORNAME"
       limit: 5
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPostsByAuthor`。撰寫的所有文章`AUTHORNAME`都應出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "6",
             "title": "A series of posts, Volume 6"
           },
           {
             "id": "4",
             "title": "A series of posts, Volume 4"
           },
           {
             "id": "2",
             "title": "A series of posts, Volume 2"
           },
           {
             "id": "7",
             "title": "A series of posts, Volume 7"
           },
           {
             "id": "1",
             "title": "A series of posts, Volume 1"
           }
         ],
         "nextToken": "<token>"
       }
     }
   }
   ```

1. 使用之前查詢傳回的值更新 `nextToken` 引數，如下所示：

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "AUTHORNAME"
       limit: 5
       nextToken: "<token>"
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPostsByAuthor`。撰寫的其餘文章`AUTHORNAME`應該會出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "8",
             "title": "A series of posts, Volume 8"
           },
           {
             "id": "5",
             "title": "A series of posts, Volume 5"
           },
           {
             "id": "3",
             "title": "A series of posts, Volume 3"
           },
           {
             "id": "9",
             "title": "A series of posts, Volume 9"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

## 使用集合
<a name="using-sets"></a>

到目前為止， `Post`類型是扁平索引鍵/值物件。您也可以使用解析程式建立複雜物件的模型，例如集合、清單和映射。讓我們將 `Post` 類型更新為包含標籤。文章可以有零或多個標籤，這些標籤會以字串集的形式存放在 DynamoDB 中。您也會設定一些變動來新增和移除標籤，以及新查詢以掃描含特定標籤的文章。

**設定您的資料**

1. 在您的 API 中，選擇**結構描述**索引標籤。

1. 修改 **Schema (結構描述)** 窗格中的 `Post` 類型以新增 `tags` 欄位，如下所示：

   ```
   type Post {
     id: ID!
     author: String
     title: String
     content: String
     url: String
     ups: Int!
     downs: Int!
     version: Int!
     tags: [String!]
   }
   ```

1. 修改 **Schema (結構描述)** 窗格中的 `Query` 類型以新增 `allPostsByTag` 查詢，如下所示：

   ```
   type Query {
     allPostsByTag(tag: String!, limit: Int, nextToken: String): PaginatedPosts!
     allPostsByAuthor(author: String!, limit: Int, nextToken: String): PaginatedPosts!
     allPost(limit: Int, nextToken: String): PaginatedPosts!
     getPost(id: ID): Post
   }
   ```

1. 修改 **Schema (結構描述)** 窗格中的 `Mutation` 類型以新增 `addTag` 和 `removeTag` 變動，如下所示：

   ```
   type Mutation {
     addTag(id: ID!, tag: String!): Post
     removeTag(id: ID!, tag: String!): Post
     deletePost(id: ID!, expectedVersion: Int): Post
     upvotePost(id: ID!): Post
     downvotePost(id: ID!): Post
     updatePost(
       id: ID!,
       author: String,
       title: String,
       content: String,
       url: String,
       expectedVersion: Int!
     ): Post
     addPost(
       author: String!,
       title: String!,
       content: String!,
       url: String!
     ): Post!
   }
   ```

1. 選擇 **Save Schema (儲存結構描述)**。

1. 在右側的**解析程式**窗格中，尋找 `Query`類型上新建立`allPostsByTag`的欄位，然後選擇**連接**。使用以下程式碼片段建立您的解析程式：

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken, tag } = ctx.arguments;
     return ddb.scan({ limit, nextToken, filter: { tags: { contains: tag } } });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

1. 儲存您對解析程式所做的任何變更。

1. 現在，`addTag`使用下面的程式碼片段對 `Mutation` 欄位執行相同的操作：
**注意**  
雖然 DynamoDB 公用程式目前不支援集合操作，但您仍可自行建置請求來與集合互動。

   ```
   import { util } from '@aws-appsync/utils'
   
   export function request(ctx) {
   	const { id, tag } = ctx.arguments
   	const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 })
   	expressionValues[':tags'] = util.dynamodb.toStringSet([tag])
   
   	return {
   		operation: 'UpdateItem',
   		key: util.dynamodb.toMapValues({ id }),
   		update: {
   			expression: `ADD tags :tags, version :plusOne`,
   			expressionValues,
   		},
   	}
   }
   
   export const response = (ctx) => ctx.result
   ```

1. 儲存對解析程式所做的任何變更。

1. `removeTag` 使用以下程式碼片段對 `Mutation` 欄位重複此操作一次：

   ```
   import { util } from '@aws-appsync/utils';
   	
   export function request(ctx) {
   	  const { id, tag } = ctx.arguments;
   	  const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 });
   	  expressionValues[':tags'] = util.dynamodb.toStringSet([tag]);
   	
   	  return {
   	    operation: 'UpdateItem',
   	    key: util.dynamodb.toMapValues({ id }),
   	    update: {
   	      expression: `DELETE tags :tags ADD version :plusOne`,
   	      expressionValues,
   	    },
   	  };
   	}
   	
   	export const response = (ctx) => ctx.resultexport
   ```

1. 儲存對解析程式所做的任何變更。

### 呼叫 API 來使用標籤
<a name="call-api-tags"></a>

現在您已設定解析程式， AWS AppSync 知道如何將傳入的 `removeTag`、 `addTag`和 `allPostsByTag`請求轉譯為 DynamoDB `UpdateItem`和 `Scan`操作。為了嘗試看看，讓我們選擇您先前建立的其中一篇文章。例如，讓我們使用 `Nadia` 撰寫的一篇文章。

**使用標籤**

1. 在您的 API 中，選擇**查詢**索引標籤。

1. 在**查詢**窗格中，新增下列查詢：

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "Nadia"
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPostsByAuthor`。

1. Nadia 的所有文章都應出現在**查詢**窗格右側的**結果**窗格中。其看起來與下列類似：

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "10",
             "title": "The cutest dog in the world"
           },
           {
             "id": "11",
             "title": "Did you known...?"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

1. 讓我們使用標題*為 全世界最可愛的狗*。記錄它，`id`因為您稍後會用到它。現在，讓我們嘗試新增`dog`標籤。

1. 在**查詢**窗格中，新增下列變動。您也必須將 `id` 引數更新為您先前記下的值。

   ```
   mutation addTag {
     addTag(id:10 tag: "dog") {
       id
       title
       tags
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `addTag`。文章會以新標籤更新：

   ```
   {
     "data": {
       "addTag": {
         "id": "10",
         "title": "The cutest dog in the world",
         "tags": [
           "dog"
         ]
       }
     }
   }
   ```

1. 您可以新增更多標籤。更新變動以將`tag`引數變更為 `puppy`：

   ```
   mutation addTag {
     addTag(id:10 tag: "puppy") {
       id
       title
       tags
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `addTag`。文章會以新標籤更新：

   ```
   {
     "data": {
       "addTag": {
         "id": "10",
         "title": "The cutest dog in the world",
         "tags": [
           "dog",
           "puppy"
         ]
       }
     }
   }
   ```

1. 您也可以刪除標籤。在**查詢**窗格中，新增下列變動。您也需要將`id`引數更新為您先前記下的值：

   ```
   mutation removeTag {
     removeTag(id:10 tag: "puppy") {
       id
       title
       tags
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `removeTag`。貼文將會更新，且 `puppy` 標籤將會刪除。

   ```
   {
     "data": {
       "addTag": {
         "id": "10",
         "title": "The cutest dog in the world",
         "tags": [
           "dog"
         ]
       }
     }
   }
   ```

1. 您也可以搜尋具有標籤的所有文章。在**查詢**窗格中，新增下列查詢：

   ```
   query allPostsByTag {
     allPostsByTag(tag: "dog") {
       posts {
         id
         title
         tags
       }
       nextToken
     }
   }
   ```

1. 選擇**執行** （橘色播放按鈕），然後選擇 `allPostsByTag`。具有 `dog` 標籤的所有文章將會傳回，如下所示：

   ```
   {
     "data": {
       "allPostsByTag": {
         "posts": [
           {
             "id": "10",
             "title": "The cutest dog in the world",
             "tags": [
               "dog",
               "puppy"
             ]
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

## 結論
<a name="conclusion-dynamodb-tutorial-js"></a>

在本教學課程中，您已建置 API，可讓您使用 AWS AppSync 和 GraphQL 在 DynamoDB 中操作`Post`物件。

若要清除，您可以從主控台刪除 AWS AppSync GraphQL API。

若要刪除與 DynamoDB 資料表相關聯的角色，請在資料來源資料表中選取**您的資料來源**，然後按一下**編輯**。在**建立或使用現有**角色下記下角色的值。前往 IAM 主控台以刪除角色。

若要刪除 DynamoDB 資料表，請按一下資料來源清單中的資料表名稱。這會帶您前往 DynamoDB 主控台，您可以在其中刪除資料表。

# 在 中使用 AWS Lambda 解析程式 AWS AppSync
<a name="tutorial-lambda-resolvers-js"></a>

您可以使用 AWS Lambda with AWS AppSync 來解析任何 GraphQL 欄位。例如，GraphQL 查詢可能會將呼叫傳送至 Amazon Relational Database Service (Amazon RDS) 執行個體，而 GraphQL 變動可能會寫入 Amazon Kinesis 串流。在本節中，我們將示範如何撰寫 Lambda 函數，根據 GraphQL 欄位操作的調用執行商業邏輯。

## 的 Powertools AWS Lambda
<a name="powertools-graphql"></a>

Powertools for AWS Lambda GraphQL 事件處理常式可簡化 Lambda 函數中 GraphQL 事件的路由和處理。它適用於 Python 和 Typescript。請參閱下列參考，進一步了解 Powertools for AWS Lambda 文件上的 GraphQL API 事件處理常式。
+ [Powertools for AWS Lambda GraphQL 事件處理常式 (Python) ](https://docs.aws.amazon.com/powertools/python/latest/core/event_handler/appsync/)
+ [Powertools for AWS Lambda GraphQL 事件處理常式 (Typescript)](https://docs.aws.amazon.com/powertools/typescript/latest/features/event-handler/appsync-graphql/) 

## 建立 Lambda 函式
<a name="create-a-lam-function-js"></a>

下列範例顯示寫入 `Node.js`（執行時間：Node.js 18.x) 的 Lambda 函數，其會在部落格文章應用程式中對部落格文章執行不同的操作。請注意，程式碼應該儲存在副檔名為 .mis 的檔案名稱中。

```
export const handler = async (event) => {
console.log('Received event {}', JSON.stringify(event, 3))

  const posts = {
1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', },
    2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', },
    3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null },
    4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', },
    5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', },
  }

  const relatedPosts = {
1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }

  console.log('Got an Invoke Request.')
  let result
  switch (event.field) {
case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

此 Lambda 函數會依 ID 擷取文章、新增文章、擷取文章清單，以及擷取特定文章的相關文章。

**注意**  
Lambda 函數使用 上的 `switch`陳述式`event.field`來判斷目前正在解析哪個欄位。

使用 AWS 管理主控台建立此 Lambda 函數。

## 設定 Lambda 的資料來源
<a name="configure-data-source-for-lamlong-js"></a>

建立 Lambda 函數後，在 AWS AppSync 主控台中導覽至 GraphQL API，然後選擇**資料來源**索引標籤。

選擇**建立資料來源**，輸入易記**的資料來源名稱** （例如 **Lambda**)，然後針對**資料來源類型**選擇 **AWS Lambda 函數**。針對**區域**，選擇與函數相同的區域。針對**函數 ARN**，選擇 Lambda 函數的 Amazon Resource Name (ARN)。

選擇 Lambda 函數後，您可以建立新的 AWS Identity and Access Management (IAM) 角色 ( AWS AppSync 會為其指派適當的許可），或選擇具有下列內嵌政策的現有角色：

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:us-east-1:111122223333:function:LAMBDA_FUNCTION"
        }
    ]
}
```

------

您還必須為 IAM 角色設定與 AWS AppSync 的信任關係，如下所示：

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

## 建立 GraphQL 結構描述
<a name="creating-a-graphql-schema-js"></a>

現在資料來源已連接至 Lambda 函數，請建立 GraphQL 結構描述。

從 AWS AppSync 主控台的結構描述編輯器，請確定您的結構描述符合下列結構描述：

```
schema {
    query: Query
    mutation: Mutation
}
type Query {
    getPost(id:ID!): Post
    allPosts: [Post]
}
type Mutation {
    addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!
}
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    relatedPosts: [Post]
}
```

## 設定解析程式
<a name="configuring-resolvers-js"></a>

現在您已註冊 Lambda 資料來源和有效的 GraphQL 結構描述，您可以使用解析程式將 GraphQL 欄位連線至 Lambda 資料來源。

您將建立使用 AWS AppSync JavaScript (`APPSYNC_JS`) 執行時間並與 Lambda 函數互動的解析程式。若要進一步了解使用 JavaScript 編寫 AWS AppSync 解析程式和函數，請參閱[解析程式和函數的 JavaScript 執行期功能](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html)。

如需 Lambda 映射範本的詳細資訊，請參閱 [Lambda 的 JavaScript 解析程式函數參考](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-lambda-js.html)。

在此步驟中，您將解析程式連接至下列欄位的 Lambda 函數：`getPost(id:ID!): Post`、`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!`、 `allPosts: [Post]`和 `Post.relatedPosts: [Post]`。從 AWS AppSync 主控台的**結構描述**編輯器，在**解析程式**窗格中，選擇 `getPost(id:ID!): Post` 欄位旁的**連接**。選擇您的 Lambda 資料來源。接著，提供下列程式碼：

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

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  return ctx.result;
}
```

此解析程式程式碼會在叫用來源物件時，將欄位名稱、引數清單和內容傳遞至 Lambda 函數。選擇**儲存**。

您已成功連接第一個解析程式。對其餘欄位重複此操作。

## 測試 GraphQL API
<a name="testing-your-graphql-api-js"></a>

現在，您的 Lambda 函式已經連接到 GraphQL 解析程式，您可以使用主控台或用戶端應用程式執行一些變動和查詢。

在 AWS AppSync 主控台的左側，選擇**查詢**，然後貼上下列程式碼：

### addPost Mutation
<a name="addpost-mutation-js"></a>

```
mutation AddPost {
    addPost(
        id: 6
        author: "Author6"
        title: "Sixth book"
        url: "https://www.amazon.com/"
        content: "This is the book is a tutorial for using GraphQL with AWS AppSync."
    ) {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### getPost Query
<a name="getpost-query-js"></a>

```
query GetPost {
    getPost(id: "2") {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### allPosts Query
<a name="allposts-query-js"></a>

```
query AllPosts {
    allPosts {
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {
            id
            title
        }
    }
}
```

## 傳回錯誤
<a name="returning-errors-js"></a>

任何指定的欄位解析度都可能導致錯誤。透過 AWS AppSync，您可以從下列來源引發錯誤：
+ 解析程式回應處理常式
+ Lambda 函式

### 從解析程式回應處理常式
<a name="from-the-resolver-response-handler-js"></a>

若要引發刻意錯誤，您可以使用 `util.error`公用程式方法。它需要 引數 `errorMessage`、 `errorType`和選用`data`值。發生錯誤時，`data` 可將額外的資料傳回給用戶端。`data` 物件會新增到 GraphQL 最後回應中的 `errors`。

下列範例顯示如何在`Post.relatedPosts: [Post]`解析程式回應處理常式中使用它。

```
// the Post.relatedPosts response handler
export function response(ctx) {
    util.error("Failed to fetch relatedPosts", "LambdaFailure", ctx.result)
    return ctx.result;
}
```

這會產生類似下列的 GraphQL 回應：

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "LambdaFailure",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "Failed to fetch relatedPosts",
            "data": [
                {
                  "id": "2",
                  "title": "Second book"
                },
                {
                  "id": "1",
                  "title": "First book"
                }
            ]
        }
    ]
}
```

其中的 `allPosts[0].relatedPosts` 為 *null*，因為錯誤以及 `errorMessage`、`errorType` 和 `data` 出現在 `data.errors[0]` 物件中。

### 從 Lambda 函式
<a name="from-the-lam-function-js"></a>

AWS AppSync 也了解 Lambda 函數擲回的錯誤。Lambda 程式設計模型可讓您引發*已處理的*錯誤。如果 Lambda 函數擲回錯誤， AWS AppSync 無法解析目前的欄位。回應中只會設定從 Lambda 傳回的錯誤訊息。目前，您無法透過從 Lambda 函數引發錯誤，將任何無關的資料傳遞回用戶端。

**注意**  
如果您的 Lambda 函數引發*未處理的*錯誤， AWS AppSync 會使用 Lambda 設定的錯誤訊息。

下列 Lambda 函式會引發錯誤：

```
export const handler = async (event) => {
  console.log('Received event {}', JSON.stringify(event, 3))
  throw new Error('I always fail.')
}
```

在您的回應處理常式中收到錯誤。您可以使用 將錯誤附加至回應，以將其傳回 GraphQL 回應`util.appendError`。若要這樣做，請將 your AWS AppSync 函數回應處理常式變更為：

```
// the lambdaInvoke response handler
export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  }
  return result;
}
```

這會傳回類似下列的 GraphQL 回應：

```
{
  "data": {
    "allPosts": null
  },
  "errors": [
    {
      "path": [
        "allPosts"
      ],
      "data": null,
      "errorType": "Lambda:Unhandled",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "I fail. always"
    }
  ]
}
```

## 進階使用案例：批次處理
<a name="advanced-use-case-batching-js"></a>

此範例中的 Lambda 函數有一個`relatedPosts`欄位，可傳回指定文章的相關文章清單。在範例查詢中，來自 Lambda 函數`allPosts`的欄位調用會傳回五個文章。因為我們指定了也要`relatedPosts`解析每個傳回的文章，所以會叫用 `relatedPosts` 欄位操作五次。

```
query {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yields 5 posts
            id
            title
        }
    }
}
```

雖然這在此特定範例中可能聽起來並不重要，但這種複合式過度擷取可能會快速破壞應用程式。

如果您要在同一查詢中，再次擷取傳回之相關 `Posts` 的 `relatedPosts`，那麼叫用的次數將大幅增加。

```
query {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yield 5 posts = 5 x 5 Posts
            id
            title
            relatedPosts {  // 5 x 5 Lambda invocations - each yield 5 posts = 25 x 5 Posts
                id
                title
                author
            }
        }
    }
}
```

在此相對簡單的查詢中， AWS AppSync 會叫用 Lambda 函數 1 \$1 5 \$1 25 = 31 次。

這是一個相當常見的挑戰，通常稱為 N\$11 問題 (在此例中，N = 5)，它可能會增加應用程式的延遲和成本。

解決此問題的方法之一，是將類似的欄位解析程式請求一起批次處理。在此範例中，它可以改為解析指定批次文章的相關文章清單，而不是讓 Lambda 函數解析單一指定文章的相關文章清單。

為了示範這一點，讓我們更新 的解析程式`relatedPosts`來處理批次處理。

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

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  }
  return result;
}
```

現在，`fieldName`當正在解決的 為 `Invoke``BatchInvoke`時，程式碼會將操作從 變更為 `relatedPosts`。現在，在設定批次處理區段中的 **函數上啟用批次處理**。將批次大小上限設定為 `5`。選擇**儲存**。

透過此變更，當解析 時`relatedPosts`，Lambda 函數會收到下列項目做為輸入：

```
[
    {
        "field": "relatedPosts",
        "source": {
            "id": 1
        }
    },
    {
        "field": "relatedPosts",
        "source": {
            "id": 2
        }
    },
    ...
]
```

在請求中指定 `BatchInvoke` 時，Lambda 函數會收到請求清單，並傳回結果清單。

具體而言，結果清單必須符合請求承載項目的大小和順序，以便 AWS AppSync 可以相應地符合結果。

在此批次範例中，Lambda 函數會傳回一批結果，如下所示：

```
[
    [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}],   // relatedPosts for id=1
    [{"id":"3","title":"Third book"}]                                     // relatedPosts for id=2
]
```

您可以更新 Lambda 程式碼來處理 的批次處理`relatedPosts`：

```
export const handler = async (event) => {
  console.log('Received event {}', JSON.stringify(event, 3))
  //throw new Error('I fail. always')

  const posts = {
    1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', },
    2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', },
    3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null },
    4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', },
    5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', },
  }

  const relatedPosts = {
    1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }
  
  if (!event.field && event.length){
    console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`);
    return event.map(e => relatedPosts[e.source.id])
  }

  console.log('Got an Invoke Request.')
  let result
  switch (event.field) {
    case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
      return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
      return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

### 傳回個別錯誤
<a name="returning-individual-errors-js"></a>

上述範例顯示，您可以從 Lambda 函數傳回單一錯誤，或從回應處理常式引發錯誤。對於批次叫用，從 Lambda 函數引發錯誤會將整個批次標記為失敗。對於發生無法復原錯誤的特定案例，例如與資料存放區的連線失敗，這可能是可接受的。不過，如果批次中的某些項目成功，而其他項目失敗，則有可能同時傳回錯誤和有效資料。由於 AWS AppSync 需要批次回應來列出符合批次原始大小的元素，因此您必須定義資料結構，以區分有效資料與錯誤。

例如，如果 Lambda 函數預期會傳回一批相關文章，您可以選擇傳回`Response`物件清單，其中每個物件都有選用*的資料*、*errorMessage* 和 *errorType* 欄位。如果出現 *errorMessage* 欄位，表示發生錯誤。

下列程式碼顯示如何更新 Lambda 函數：

```
export const handler = async (event) => {
console.log('Received event {}', JSON.stringify(event, 3))
  // throw new Error('I fail. always')
const posts = {
1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', },
    2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', },
    3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null },
    4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', },
    5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', },
  }

  const relatedPosts = {
1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }
  
  if (!event.field && event.length){
console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`);
    return event.map(e => {
// return an error for post 2
if (e.source.id === '2') {
return { 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' }
      }
      return {data: relatedPosts[e.source.id]}
      })
  }

  console.log('Got an Invoke Request.')
  let result
  switch (event.field) {
case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

更新`relatedPosts`解析程式程式碼：

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

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  } else if (result.errorMessage) {
    util.appendError(result.errorMessage, result.errorType, result.data)
  } else if (ctx.info.fieldName === 'relatedPosts') {
      return result.data
  } else {
      return result
  }
}
```

回應處理常式現在會檢查`Invoke`操作時 Lambda 函數傳回的錯誤、檢查`BatchInvoke`操作個別項目傳回的錯誤，最後檢查 `fieldName`。對於 `relatedPosts`，函數會傳回 `result.data`。對於所有其他欄位，函數只會傳回 `result`。例如，請參閱下列查詢：

```
query AllPosts {
  allPosts {
    id
    title
    content
    url
    ups
    downs
    relatedPosts {
      id
    }
    author
  }
}
```

此查詢會傳回類似下列的 GraphQL 回應：

```
{
  "data": {
    "allPosts": [
      {
        "id": "1",
        "relatedPosts": [
          {
            "id": "4"
          }
        ]
      },
      {
        "id": "2",
        "relatedPosts": null
      },
      {
        "id": "3",
        "relatedPosts": [
          {
            "id": "2"
          },
          {
            "id": "1"
          }
        ]
      },
      {
        "id": "4",
        "relatedPosts": [
          {
            "id": "2"
          },
          {
            "id": "1"
          }
        ]
      },
      {
        "id": "5",
        "relatedPosts": []
      }
    ]
  },
  "errors": [
    {
      "path": [
        "allPosts",
        1,
        "relatedPosts"
      ],
      "data": null,
      "errorType": "ERROR",
      "errorInfo": null,
      "locations": [
        {
          "line": 4,
          "column": 5,
          "sourceName": null
        }
      ],
      "message": "Error Happened"
    }
  ]
}
```

### 設定批次大小上限
<a name="configure-max-batch-size-js"></a>

若要在解析程式上設定批次大小上限，請在 AWS Command Line Interface () 中使用下列命令AWS CLI：

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --code "<code-goes-here>" \
 --runtime name=APPSYNC_JS,runtimeVersion=1.0.0 \
 --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

**注意**  
提供請求映射範本時，您必須使用 `BatchInvoke`操作來使用批次處理。

# 使用本機解析程式 in AWS AppSync
<a name="tutorial-local-resolvers-js"></a>

AWS AppSync 可讓您使用支援的資料來源 (AWS Lambda、Amazon DynamoDB 或 Amazon OpenSearch Service) 來執行各種操作。不過，在某些情況下，可能不需要呼叫支援的資料來源。

這就是本機解析程式的實用之處。本機解析程式只會將請求處理常式的結果**轉送**到回應處理常式，而不是呼叫遠端資料來源。欄位解析不會 leave AWS AppSync。

本機解析程式在許多情況下都很有用。最常見的使用案例是發佈通知而不觸發資料來源呼叫。為了示範此使用案例，讓我們建置一個 pub/sub 應用程式，使用者可以在其中發佈和訂閱訊息。此範例使用*訂閱*，因此，如果您不熟悉*訂閱*，可以遵循[即時資料](aws-appsync-real-time-data.md)教學課程。

## 建立 pub/sub 應用程式
<a name="create-the-pub-sub-application-js"></a>

首先，選擇**從頭開始設計**選項，並在建立 GraphQL API 時設定選用的詳細資訊，以建立空白的 GraphQL API。

在我們的 pub/sub 應用程式中，用戶端可以訂閱和發佈訊息。每個已發佈的訊息都包含名稱和資料。將此新增至結構描述：

```
type Channel {
	name: String!
	data: AWSJSON!
}

type Mutation {
	publish(name: String!, data: AWSJSON!): Channel
}

type Query {
	getChannel: Channel
}

type Subscription {
	subscribe(name: String!): Channel
		@aws_subscribe(mutations: ["publish"])
}
```

接下來，讓我們將解析程式連接到 `Mutation.publish` 欄位。在**結構描述**窗格旁的**解析程式**窗格中，尋找 `Mutation` 類型，然後尋找 `publish(...): Channel` 欄位，然後按一下**連接**。

建立*無*資料來源並將其命名為 *PageDataSource*。將它連接到您的解析程式。

使用下列程式碼片段新增您的解析程式實作：

```
export function request(ctx) {
  return { payload: ctx.args };
}

export function response(ctx) {
  return ctx.result;
}
```

請確定您建立解析程式並儲存所做的變更。

## 傳送和訂閱訊息
<a name="send-and-subscribe-to-messages-js"></a>

若要讓用戶端接收訊息，他們必須先訂閱收件匣。

在**查詢**窗格中，執行`SubscribeToData`訂閱：

```
subscription SubscribeToData {
    subscribe(name:"channel") {
        name
        data
    }
}
```

 每當叫用`publish`變動時，訂閱者都會收到訊息，但只有在訊息傳送到`channel`訂閱時才會收到訊息。讓我們在**查詢**窗格中試試看。當您的訂閱仍在主控台中執行時，請開啟另一個主控台，並在**查詢**窗格中執行下列請求：

**注意**  
在此範例中，我們使用有效的 JSON 字串。

```
mutation PublishData {
    publish(data: "{\"msg\": \"hello world!\"}", name: "channel") {
        data
        name
    }
}
```

結果看起來像這樣：

```
{
  "data": {
    "publish": {
      "data": "{\"msg\":\"hello world!\"}",
      "name": "channel"
    }
  }
}
```

我們剛示範如何使用本機解析程式，方法是發佈訊息並接收訊息，而不離開 the AWS AppSync 服務。

# 在 中結合 GraphQL 解析程式 AWS AppSync
<a name="tutorial-combining-graphql-resolvers-js"></a>

GraphQL 結構描述中的解析程式與欄位具有一對一的關係，以及高度的彈性。由於資料來源是在獨立於結構描述的解析程式上設定，因此您能夠透過不同的資料來源解析或操作 GraphQL 類型，讓您混合並比對結構描述，以最符合您的需求。

下列案例示範如何在結構描述中混合和比對資料來源。開始之前，您應該熟悉為 Amazon DynamoDB 和 Amazon OpenSearch Service AWS Lambda設定資料來源和解析程式。

## 範例結構描述
<a name="example-schema-js"></a>

下列結構描述的類型為 `Post`，每個都有三個 `Query`和 `Mutation`操作：

```
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    version: Int!
}

type Query {
    allPost: [Post]
    getPost(id: ID!): Post
    searchPosts: [Post]
}

type Mutation {
    addPost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String
    ): Post
    updatePost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String,
        ups: Int!,
        downs: Int!,
        expectedVersion: Int!
    ): Post
    deletePost(id: ID!): Post
}
```

在此範例中，您總共會有六個解析程式，每個解析程式都需要資料來源。解決此問題的一種方法是將這些關聯到稱為 的單一 Amazon DynamoDB 資料表`Posts`，其中 `AllPost` 欄位執行掃描， `searchPosts` 欄位執行查詢 （請參閱 [ DynamoDB 的 JavaScript 解析程式函數參考](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html))。不過，您不限於 Amazon DynamoDB；Lambda 或 OpenSearch Service 等不同資料來源的存在，可滿足您的業務需求。

## 透過解析程式變更資料
<a name="alter-data-through-resolvers-js"></a>

您可能需要從 AWS AppSync 資料來源未直接支援的第三方資料庫傳回結果。您可能還必須對資料執行複雜的修改，才能傳回 API 用戶端 (API)。這可能是由於資料類型格式不正確所致，例如用戶端的時間戳記差異，或處理回溯相容性問題。在這種情況下，將 AWS Lambda 函數作為資料來源連接到您的 AWS AppSync API 是適當的解決方案。為了說明起見，在下列範例中， AWS Lambda 函數會操作從第三方資料存放區擷取的資料：

```
export const handler = (event, context, callback) => {
    // fetch data
    const result = fetcher()

    // apply complex business logic
    const data = transform(result)	

    // return to AppSync
    return data
};
```

這是一個非常有效的 Lambda 函式，可以連結至 GraphQL 結構描述中的 `AllPost` 欄位，如此，任何傳回所有結果的查詢，都會取得支持票和不支持票票數的隨機數量。

## DynamoDB 和 OpenSearch Service
<a name="ddb-and-es-js"></a>

對於某些應用程式，您可以對 DynamoDB 執行變動或簡單查詢，並將背景程序傳輸文件到 OpenSearch Service。您可以直接將`searchPosts`解析程式連接至 OpenSearch Service 資料來源，並使用 GraphQL 查詢傳回搜尋結果 （來自源自 DynamoDB 的資料）。這在將進階搜尋操作新增至您的應用程式時非常強大，例如關鍵字、模糊字詞配對，甚至是地理空間查詢。可以透過 ETL 程序從 DynamoDB 傳輸資料，或者，您可以使用 Lambda 從 DynamoDB 串流。

若要開始使用這些特定資料來源，請參閱我們的 [DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-resolvers-js.html) 和 [Lambda](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers-js.html) 教學課程。

例如，使用先前教學課程中的結構描述，下列變動會將項目新增至 DynamoDB：

```
mutation addPost {
  addPost(
    id: 123
    author: "Nadia"
    title: "Our first post!"
    content: "This is our first post."
    url: "https://aws.amazon.com/appsync/"
  ) {
    id
    author
    title
    content
    url
    ups
    downs
    version
  }
}
```

這會將資料寫入 DynamoDB，然後透過 Lambda 將資料串流至 Amazon OpenSearch Service，然後用於依不同欄位搜尋文章。例如，由於資料位於 Amazon OpenSearch Service 中，因此您可以使用自由格式文字搜尋作者或內容欄位，即使是空格，如下所示：

```
query searchName{
    searchAuthor(name:"   Nadia   "){
        id
        title
        content
    }
}

---------- or ----------

query searchContent{
    searchContent(text:"test"){
        id
        title
        content
    }
}
```

由於資料是直接寫入 DynamoDB，因此您仍然可以使用 `allPost{...}`和 `getPost{...}`查詢對資料表執行有效的清單或項目查詢操作。此堆疊針對 DynamoDB 串流使用以下範例程式碼：

**注意**  
此 Python 程式碼是範例，並非用於生產程式碼。

```
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = '' # e.g. us-east-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)

host = '' # the OpenSearch Service domain, e.g. https://search-mydomain.us-west-1.es.amazonaws.com
index = 'lambda-index'
datatype = '_doc'
url = host + '/' + index + '/' + datatype + '/'

headers = { "Content-Type": "application/json" }

def handler(event, context):
    count = 0
    for record in event['Records']:
        # Get the primary key for use as the OpenSearch ID
        id = record['dynamodb']['Keys']['id']['S']

        if record['eventName'] == 'REMOVE':
            r = requests.delete(url + id, auth=awsauth)
        else:
            document = record['dynamodb']['NewImage']
            r = requests.put(url + id, auth=awsauth, json=document, headers=headers)
        count += 1
    return str(count) + ' records processed.'
```

然後，您可以使用 DynamoDB 串流將其連接到主索引鍵為 的 DynamoDB 資料表`id`，而 DynamoDB 來源的任何變更都會串流到您的 OpenSearch Service 網域。如需進行這項設定的詳細資訊，請參閱 [DynamoDB 串流文件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html)。

# 使用 Amazon OpenSearch Service 解析程式 in AWS AppSync
<a name="tutorial-elasticsearch-resolvers-js"></a>

AWS AppSync 支援從您在自己的 AWS 帳戶中佈建的網域使用 Amazon OpenSearch Service，前提是這些網域不存在於 VPC 中。在佈建網域之後，您可以使用資料來源來連線到這些網域，此時您可以在結構描述中設定解析程式，來進行 GraphQL 操作，例如查詢、變動和訂閱。本教學課程將會逐步說明一些常見的範例。

如需詳細資訊，請參閱 [ OpenSearch 的 JavaScript 解析程式函數參考](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-elasticsearch-js.html)。

## 建立新的 OpenSearch Service 網域
<a name="create-a-new-es-domain-js"></a>

若要開始使用本教學課程，您需要現有的 OpenSearch Service 網域。如果尚未擁有，您可以使用下列範例。請注意，建立 OpenSearch Service 網域最多可能需要 15 分鐘，然後才能繼續將其與 an AWS AppSync 資料來源整合。

```
aws cloudformation create-stack --stack-name AppSyncOpenSearch \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml \
--parameters ParameterKey=OSDomainName,ParameterValue=ddtestdomain ParameterKey=Tier,ParameterValue=development \
--capabilities CAPABILITY_NAMED_IAM
```

您可以在帳戶中 AWS 的 US-West-2 （奧勒岡） 區域中啟動下列 AWS CloudFormation 堆疊：

 [https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml)

## 設定 OpenSearch Service 的資料來源
<a name="configure-data-source-for-es-js"></a>

建立 OpenSearch Service 網域後，導覽至 your AWS AppSync GraphQL API，然後選擇**資料來源**索引標籤。選擇**建立資料來源**，然後輸入資料來源的易記名稱，例如「*oss*」。然後，針對**資料來源類型**選擇 **Amazon OpenSearch 網域**，選擇適當的區域，您應該會看到列出的 OpenSearch Service 網域。選取之後，您可以建立新的角色，而 AWS AppSync 會指派適合角色的許可，也可以選擇具有下列內嵌政策的現有角色：

您還需要為該角色設定與 AWS AppSync 的信任關係：

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

此外，OpenSearch Service 網域有自己的**存取政策**，您可以透過 Amazon OpenSearch Service 主控台進行修改。您必須為 OpenSearch Service 網域新增類似下列的政策，以及適當的動作和資源。請注意，**主體**將是 AWS AppSync 資料來源角色，如果您讓該主控台建立，可以在 IAM 主控台中找到該角色。

## 連接解析程式
<a name="connecting-a-resolver-js"></a>

現在資料來源已連線至 OpenSearch Service 網域，您可以使用解析程式將其連線至 GraphQL 結構描述，如下列範例所示：

```
 type Query {
   getPost(id: ID!): Post
   allPosts: [Post]
 }

 type Mutation {
   addPost(id: ID!, author: String, title: String, url: String, ups: Int, downs: Int, content: String): AWSJSON
 }

type Post {
  id: ID!
  author: String
  title: String
  url: String
  ups: Int
  downs: Int
  content: String
}
```

請注意，有使用者定義的 `Post` 類型與 `id` 欄位。在下列範例中，我們假設有一個程序 （可自動化） 可將此類型放入您的 OpenSearch Service 網域，這會映射到 索引`/post/_doc``post`所在的路徑根目錄。在此根路徑中，您可以執行個別文件搜尋、使用 進行萬用字元搜尋`/id/post*`，或使用 的路徑進行多文件搜尋`/post/_search`。例如，如果您有另一種名為 的類型`User`，您可以在名為 的新索引下編製文件索引`user`，然後使用**路徑** 執行搜尋`/user/_search`。

從 AWS AppSync `Posts` 主控台的**結構描述**編輯器中，修改先前的結構描述以包含`searchPosts`查詢：

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  searchPosts: [Post]
}
```

儲存結構描述。在**解析程式**窗格中，尋找`searchPosts`並選擇**連接**。選擇您的 OpenSearch Service 資料來源並儲存解析程式。使用以下程式碼片段更新解析程式的程式碼：

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

/**
 * Searches for documents by using an input term
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: `/post/_search`,
		params: { body: { from: 0, size: 50 } },
	}
}

/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result.hits.hits.map((hit) => hit._source)
}
```

這會假設上述結構描述的文件已在 OpenSearch Service 的 `post` 欄位下編製索引。如果您以不同的方式建構資料，您將需要相應地更新。

## 修改您的搜尋
<a name="modifying-your-searches-js"></a>

上述解析程式請求處理常式會針對所有記錄執行簡單的查詢。假設您想要根據特定的作者來進行搜尋。此外，假設您希望該作者成為 GraphQL 查詢中定義的引數。在 AWS AppSync 主控台的**結構描述**編輯器中，新增`allPostsByAuthor`查詢：

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  allPostsByAuthor(author: String!): [Post]
  searchPosts: [Post]
}
```

在**解析程式**窗格中，尋找`allPostsByAuthor`並選擇**連接**。選擇 OpenSearch Service 資料來源，並使用下列程式碼：

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

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: '/post/_search',
		params: {
			body: {
				from: 0,
				size: 50,
				query: { match: { author: ctx.args.author } },
			},
		},
	}
}

/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result.hits.hits.map((hit) => hit._source)
}
```

請注意，`body` 會填入 `author` 欄位的查詢詞語，此欄位會做為引數，從用戶端直接傳來。或者，您可以使用預先填入的資訊，例如標準文字。

## 將資料新增至 OpenSearch Service
<a name="adding-data-to-es-js"></a>

由於 GraphQL 變動，您可能想要將資料新增至 OpenSearch Service 網域。這是一個具有搜尋和其他用途的強大機制。由於您可以使用 GraphQL 訂閱[讓資料即時](aws-appsync-real-time-data.md)，因此它可以做為通知用戶端 OpenSearch Service 網域中資料更新的機制。

返回 AWS AppSync 主控台中的**結構描述**頁面，然後選取**附加**以進行`addPost()`變動。再次選取 OpenSearch Service 資料來源，並使用下列程式碼：

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

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'PUT',
		path: `/post/_doc/${ctx.args.id}`,
		params: { body: ctx.args },
	}
}

/**
 * Returns the inserted post
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result
}
```

如同之前，這是資料結構的範例。如果您有不同的欄位名稱或索引，則需要更新 `path`和 `body`。此範例也說明如何在請求處理常式中使用 `ctx.args`，`context.arguments`也可以寫入為 。

## 擷取單一文件
<a name="retrieving-a-single-document-js"></a>

最後，如果您想要在結構描述中使用`getPost(id:ID)`查詢來傳回個別文件，請在 AWS AppSync 主控台的**結構描述**編輯器中尋找此查詢，然後選擇**連接**。再次選取 OpenSearch Service 資料來源，並使用下列程式碼：

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

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: `/post/_doc/${ctx.args.id}`,
	}
}

/**
 * Returns the post
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result._source
}
```

## 執行查詢和變動
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations-js"></a>

您現在應該能夠對 OpenSearch Service 網域執行 GraphQL 操作。導覽至 AWS AppSync 主控台的**查詢**索引標籤，並新增記錄：

```
mutation AddPost {
    addPost (
        id:"12345"
        author: "Fred"
        title: "My first book"
        content: "This will be fun to write!"
        url: "publisher website",
        ups: 100,
        downs:20 
       )
}
```

您會在右側看到變動的結果。同樣地，您現在可以針對 OpenSearch Service 網域執行`searchPosts`查詢：

```
query search {
    searchPosts {
        id
        title
        author
        content
    }
}
```

## 最佳實務
<a name="best-practices-js"></a>
+ OpenSearch Service 應該用於查詢資料，而不是做為您的主要資料庫。您可能想要將 OpenSearch Service 與 Amazon DynamoDB 搭配使用，如[合併 GraphQL 解析程式](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-combining-graphql-resolvers-js.html)中所述。
+ 只有在允許 AWS AppSync 服務角色存取叢集時，才允許 存取您的網域。
+ 您可以在開發時少量使用最低成本的叢集，然後在進入正式生產時，轉移到具有高可用性 (HA) 的較大型叢集。

# 在 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 交易的使用方式。

# 在 AWS AppSync 中使用 DynamoDB 批次操作
<a name="tutorial-dynamodb-batch-js"></a>

AWS AppSync 支援在單一區域中的一或多個資料表中使用 Amazon DynamoDB 批次操作。支援的操作包括 `BatchGetItem`、`BatchPutItem` 和 `BatchDeleteItem`。透過使用這些 in AWS AppSync 功能，您可以執行下列任務：
+ 在單一查詢中傳遞金鑰清單，並從資料表傳回結果
+ 在單一查詢中從一或多個資料表讀取記錄
+ 大量寫入記錄至一或多個資料表
+ 有條件地在可能有關聯的多個資料表中寫入或刪除記錄

批次操作 in AWS AppSync 與非批次操作有兩個主要差異：
+ 資料來源角色必須具有解析程式將存取的所有資料表的許可。
+ 解析程式的資料表規格是請求物件的一部分。

## 單一資料表批次
<a name="single-table-batch-js"></a>

**警告**  
`BatchPutItem` 與衝突偵測和解決搭配使用時，不支援 和 `BatchDeleteItem` 。必須停用這些設定，以防止可能的錯誤。

若要開始使用，讓我們建立新的 GraphQL API。在 AWS AppSync 主控台中，選擇從頭開始**建立 API**、**GraphQL APIs **和設計。 ****為您的 API 命名`BatchTutorial API`，選擇**下一步**，然後在**指定 GraphQL 資源**步驟上，選擇**稍後建立 GraphQL 資源**，然後按一下**下一步**。檢閱您的詳細資訊並建立 API。前往**結構描述**頁面並貼上下列結構描述，請注意，對於查詢，我們將傳遞 IDs 清單：

```
type Post {
    id: ID!
    title: String
}

input PostInput {
    id: ID!
    title: String
}

type Query {
    batchGet(ids: [ID]): [Post]
}

type Mutation {
    batchAdd(posts: [PostInput]): [Post]
    batchDelete(ids: [ID]): [Post]
}
```

儲存您的結構描述，然後選擇頁面頂端的**建立資源**。選擇**使用現有類型**，然後選取`Post`類型。為您的資料表命名 `Posts`。確定**主索引鍵**設定為 `id`，取消選取**自動產生 GraphQL** （您將提供自己的程式碼），然後選取**建立**。為了協助您開始使用， AWS AppSync 會建立新的 DynamoDB 資料表，以及連接至具有適當角色之資料表的資料來源。不過，您仍然需要將一些許可新增至角色。前往**資料來源**頁面，然後選擇新的資料來源。在**選取現有角色**下，您會注意到已自動為資料表建立角色。請記下角色 （看起來像 `appsync-ds-ddb-aaabbbcccddd-Posts`)，然後前往 IAM 主控台 ([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)：//)。在 IAM 主控台中，選擇**角色**，然後從資料表中選擇您的角色。在您的角色中，在**許可政策**下，按一下政策旁的「`+`」（應該具有與角色名稱類似的名稱）。當政策出現時，選擇可摺疊頂端的**編輯**。您需要將批次許可新增至政策，特別是 `dynamodb:BatchGetItem`和 `dynamodb:BatchWriteItem`。這看起來會像這樣：

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

****  

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

------

選擇**下一步**，然後選擇**儲存變更**。您的政策現在應該允許批次處理。

返回 AWS AppSync 主控台，前往**結構描述**頁面，然後選取 `Mutation.batchAdd` 欄位旁的**連接**。使用 `Posts`資料表做為資料來源來建立您的解析程式。在程式碼編輯器中，將處理常式取代為下面的程式碼片段。此程式碼片段會自動取得 GraphQL `input PostInput`類型中的每個項目，並建置 `BatchPutItem`操作所需的映射：

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

export function request(ctx) {
  return {
    operation: "BatchPutItem",
    tables: {
      Posts: ctx.args.posts.map((post) => util.dynamodb.toMapValues(post)),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

導覽至 AWS AppSync 主控台的**查詢**頁面，並執行下列`batchAdd`變動：

```
mutation add {
    batchAdd(posts:[{
            id: 1 title: "Running in the Park"},{
            id: 2 title: "Playing fetch"
        }]){
            id
            title
    }
}
```

您應該會在畫面上看到列印的結果；這可以透過檢閱 DynamoDB 主控台來掃描寫入`Posts`資料表的值來驗證。

接下來，重複連接解析程式的程序，但對於使用`Posts`資料表做為資料來源`Query.batchGet`的欄位。使用下列程式碼取代處理常式。這會自動擷取 GraphQL `ids:[]` 類型的每個項目，並建置 `BatchGetItem` 作業所需的對應圖：

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

export function request(ctx) {
  return {
    operation: "BatchGetItem",
    tables: {
      Posts: {
        keys: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
        consistentRead: true,
      },
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

現在，返回 AWS AppSync 主控台的**查詢**頁面，並執行下列`batchGet`查詢：

```
query get {
    batchGet(ids:[1,2,3]){
        id
        title
    }
}
```

這應該會針對您先前所新增的兩個 `id` 值，傳回其結果。請注意，已傳回`null`值`id`為 的 值`3`。這是因為您的`Posts`資料表中尚無具有該值的記錄。另請注意， AWS AppSync 會以與傳遞給查詢的金鑰相同的順序傳回結果，這是 AWS AppSync 代表您執行的額外功能。因此，如果您切換到 `batchGet(ids:[1,3,2])`，您會看到訂單已變更。您也會知道哪個 `id` 傳回 `null` 值。

最後，使用`Posts`資料表做為資料來源，將另一個解析程式連接到 `Mutation.batchDelete` 欄位。使用下列程式碼取代處理常式。這會自動擷取 GraphQL `ids:[]` 類型的每個項目，並建置 `BatchGetItem` 作業所需的對應圖：

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

export function request(ctx) {
  return {
    operation: "BatchDeleteItem",
    tables: {
      Posts: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

現在，返回 AWS AppSync 主控台的**查詢**頁面，並執行下列`batchDelete`變動：

```
mutation delete {
    batchDelete(ids:[1,2]){ id }
}
```

`id` `1` 和 `2` 的記錄現在應已刪除。如果重新執行先前的 `batchGet()` 查詢，這些應該會傳回 `null`。

## 多資料表批次
<a name="multi-table-batch-js"></a>

**警告**  
`BatchPutItem` 與衝突偵測和解決搭配使用時，不支援 和 `BatchDeleteItem` 。必須停用這些設定，以防止可能的錯誤。

AWS AppSync 也可讓您跨資料表執行批次操作。來試試建置更複雜的應用程式。試想我們正在建置寵物運作狀態應用程式，其中感應器會報告寵物的位置和身體溫度。感應器是由電池供電，而且每隔幾分鐘就會試著連線到網路。當感應器建立連線時，它會將其讀數傳送至 our AWS AppSync API。觸發條件接著就會分析資料，然後將儀表板呈現給寵物的主人。讓我們著重介紹感應器與後端資料存放區之間的互動。

在 AWS AppSync 主控台中，選擇從頭開始**建立 API**、**GraphQL APIs **和設計。 ****為您的 API 命名`MultiBatchTutorial API`，選擇**下一步**，然後在**指定 GraphQL 資源**步驟上，選擇**稍後建立 GraphQL 資源**，然後按一下**下一步**。檢閱您的詳細資訊並建立 API。前往**結構描述**頁面，貼上並儲存下列結構描述：

```
type Mutation {
    # Register a batch of readings
    recordReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
    # Delete a batch of readings
    deleteReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
}

type Query {
    # Retrieve all possible readings recorded by a sensor at a specific time
    getReadings(sensorId: ID!, timestamp: String!): [SensorReading]
}

type RecordResult {
    temperatureReadings: [TemperatureReading]
    locationReadings: [LocationReading]
}

interface SensorReading {
    sensorId: ID!
    timestamp: String!
}

# Sensor reading representing the sensor temperature (in Fahrenheit)
type TemperatureReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    value: Float
}

# Sensor reading representing the sensor location (lat,long)
type LocationReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    lat: Float
    long: Float
}

input TemperatureReadingInput {
    sensorId: ID!
    timestamp: String
    value: Float
}

input LocationReadingInput {
    sensorId: ID!
    timestamp: String
    lat: Float
    long: Float
}
```

我們需要建立兩個 DynamoDB 資料表：
+ `locationReadings` 將存放感應器位置讀數。
+ `temperatureReadings` 將存放感應器溫度讀數。

兩個資料表將共用相同的主索引鍵結構： `sensorId (String)` 作為分割區索引鍵， `timestamp (String)`作為排序索引鍵。

選擇頁面頂端的**建立資源**。選擇**使用現有類型**，然後選取`locationReadings`類型。為您的資料表命名 `locationReadings`。確定**主索引鍵**設定為 `sensorId`，排序索引鍵設定為 `timestamp`。取消選取**自動產生 GraphQL** （您將提供自己的程式碼），然後選取**建立**。`temperatureReadings` 使用 `temperatureReadings`作為類型和資料表名稱，重複此程序。使用與上述相同的金鑰。

您的新資料表將包含自動產生的角色。您仍然需要將一些許可新增至這些角色。前往**資料來源**頁面，然後選擇 `locationReadings`。在**選取現有角色**下，您可以看到角色。請記下角色 （看起來像 `appsync-ds-ddb-aaabbbcccddd-locationReadings`)，然後前往 IAM 主控台 ([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)：//)。在 IAM 主控台中，選擇**角色**，然後從資料表中選擇您的角色。在您的角色中，在**許可政策**下，按一下政策旁的「`+`」（應該具有與角色名稱類似的名稱）。當政策出現時，選擇可摺疊頂端的**編輯**。您需要將許可新增至此政策。這看起來會像這樣：

選擇**下一步**，然後選擇**儲存變更**。使用上述相同的政策程式碼片段，為`temperatureReadings`資料來源重複此程序。

### BatchPutItem - 記錄感應器讀數
<a name="batchputitem-recording-sensor-readings-js"></a>

感應器必須能夠在連線到網際網路之後傳送其讀數。感應器將使用 GraphQL 欄位 `Mutation.recordReadings` 這個 API 來執行此項動作。我們需要將解析程式新增至此欄位。

在 AWS AppSync 主控台的**結構描述**頁面中，選取 `Mutation.recordReadings` 欄位旁的**連接**。在下一個畫面上，使用 `locationReadings`資料表做為資料來源來建立您的解析程式。

建立解析程式之後，請在編輯器中將處理常式取代為下列程式碼。`BatchPutItem` 此操作允許我們指定多個資料表：

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

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const locationReadings = locReadings.map((loc) => util.dynamodb.toMapValues(loc))
	const temperatureReadings = tempReadings.map((tmp) => util.dynamodb.toMapValues(tmp))

	return {
		operation: 'BatchPutItem',
		tables: {
			locationReadings,
			temperatureReadings,
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

進行批次操作時，呼叫可能會同時傳回錯誤和結果。在這種情況中，我們可以隨意進行一些額外的錯誤處理。

**注意**  
的使用`utils.appendError()`類似於 `util.error()`，主要區別在於它不會中斷請求或回應處理常式的評估。相反地，它表示 欄位發生錯誤，但允許評估處理常式，並將資料傳回給發起人。當您的應用程式需要傳回部分結果`utils.appendError()`時，建議您使用 。

儲存解析程式並導覽至 AWS AppSync 主控台中的**查詢**頁面。我們現在可以傳送一些感應器讀數。

執行下列的變動：

```
mutation sendReadings {
  recordReadings(
    tempReadings: [
      {sensorId: 1, value: 85.5, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, value: 85.7, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, value: 85.8, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, value: 84.2, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, value: 81.5, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]
    locReadings: [
      {sensorId: 1, lat: 47.615063, long: -122.333551, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, lat: 47.615163, long: -122.333552, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, lat: 47.615263, long: -122.333553, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, lat: 47.615363, long: -122.333554, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, lat: 47.615463, long: -122.333555, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

我們在一個變動中傳送十個感應器讀數，並將讀數分割為兩個資料表。使用 DynamoDB 主控台來驗證資料是否同時顯示在 `locationReadings`和 `temperatureReadings`資料表中。

### BatchDeleteItem - 刪除感應器讀數
<a name="batchdeleteitem-deleting-sensor-readings-js"></a>

同樣地，我們也需要能夠刪除感應器讀數的批次。讓我們使用 `Mutation.deleteReadings` GraphQL 欄位來進行這項動作。在 AWS AppSync 主控台的**結構描述**頁面中，選取 `Mutation.deleteReadings` 欄位旁的**連接**。在下一個畫面上，使用 `locationReadings`資料表做為資料來源來建立您的解析程式。

建立解析程式之後，請將程式碼編輯器中的處理常式取代為下方的程式碼片段。在此解析程式中，我們使用協助程式函數映射器，從提供的輸入`timestamp`中擷取 `sensorId`和 。

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

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const mapper = ({ sensorId, timestamp }) => util.dynamodb.toMapValues({ sensorId, timestamp })

	return {
		operation: 'BatchDeleteItem',
		tables: {
			locationReadings: locReadings.map(mapper),
			temperatureReadings: tempReadings.map(mapper),
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

儲存解析程式並導覽至 AWS AppSync 主控台中的**查詢**頁面。現在，讓我們刪除幾個感應器讀數。

執行下列的變動：

```
mutation deleteReadings {
  # Let's delete the first two readings we recorded
  deleteReadings(
    tempReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]
    locReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

**注意**  
不同於 `DeleteItem` 操作，回應中未傳回完整刪除的項目。只會傳回已傳遞的索引鍵。若要進一步了解，請參閱 [ DynamoDB 的 JavaScript 解析程式函數參考中的 BatchDeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item)。

透過 DynamoDB 主控台驗證這兩個讀數是否已從 `locationReadings`和 `temperatureReadings`資料表中刪除。

### BatchGetItem - 擷取讀數
<a name="batchgetitem-retrieve-readings-js"></a>

我們應用程式的另一個常見操作是在特定時間點擷取感應器的讀數。試試將解析程式連接到結構描述中的 `Query.getReadings` GraphQL 欄位。在 AWS AppSync 主控台的**結構描述**頁面中，選取 `Query.getReadings` 欄位旁的**連接**。在下一個畫面上，使用 `locationReadings`資料表做為資料來源來建立您的解析程式。

讓我們使用下列程式碼：

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

export function request(ctx) {
	const keys = [util.dynamodb.toMapValues(ctx.args)]
	const consistentRead = true
	return {
		operation: 'BatchGetItem',
		tables: {
			locationReadings: { keys, consistentRead },
			temperatureReadings: { keys, consistentRead },
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	const { locationReadings: locs, temperatureReadings: temps } = ctx.result.data

	return [
		...locs.map((l) => ({ ...l, __typename: 'LocationReading' })),
		...temps.map((t) => ({ ...t, __typename: 'TemperatureReading' })),
	]
}
```

儲存解析程式並導覽至 AWS AppSync 主控台中的**查詢**頁面。現在，讓我們擷取感應器讀數。

執行下列的查詢：

```
query getReadingsForSensorAndTime {
  # Let's retrieve the very first two readings
  getReadings(sensorId: 1, timestamp: "2018-02-01T17:21:06.000+08:00") {
    sensorId
    timestamp
    ...on TemperatureReading {
      value
    }
    ...on LocationReading {
      lat
      long
    }
  }
}
```

我們已成功示範如何使用 AWS AppSync 進行 DynamoDB 批次操作。

## 錯誤處理
<a name="error-handling-js"></a>

In AWS AppSync 中，資料來源操作有時可能會傳回部分結果。我們將會使用部分結果這個詞彙，來表示操作的輸出包含一些資料和錯誤的情況。由於錯誤處理本質上是應用程式特定的， AWS AppSync 讓您有機會處理回應處理常式中的錯誤。如果發生解析程式呼叫錯誤，在文字內容中會出現 `ctx.error`。呼叫錯誤一律包含訊息和類型，可做為 `ctx.error.message` 和 `ctx.error.type` 屬性存取。在回應處理常式中，您可以透過三種方式處理部分結果：

1. 只需傳回資料，即可省略呼叫錯誤。

1. 透過停止處理常式評估來引發錯誤 （使用 `util.error(...)`)，這不會傳回任何資料。

1. 附加錯誤 （使用 `util.appendError(...)`) 並傳回資料。

讓我們使用 DynamoDB 批次操作來示範上述三個點的每一個。

### DynamoDB 批次操作
<a name="dynamodb-batch-operations-js"></a>

使用 DynamoDB 批次操作時，批次作業就有可能部分完成。也就是說，請求的項目或索引鍵可以有一些尚未處理完成。If AWS AppSync 無法完成批次，未處理的項目將在內容上設定調用錯誤。

我們將會使用 `Query.getReadings` 欄位組態 (取自於本教學課程先前區段所說明的 `BatchGetItem` 操作) 來建置錯誤處理功能。這次，讓我們假設在執行 `Query.getReadings` 欄位時，`temperatureReadings` DynamoDB 資料表用盡了佈建的輸送量。DynamoDB 在第二次嘗試 by AWS AppSync `ProvisionedThroughputExceededException`期間引發 ，以處理批次中剩餘的元素。

下列 JSON 代表 DynamoDB 批次調用之後但在呼叫回應處理常式之前序列化的內容：

```
{
  "arguments": {
    "sensorId": "1",
    "timestamp": "2018-02-01T17:21:05.000+08:00"
  },
  "source": null,
  "result": {
    "data": {
      "temperatureReadings": [
        null
      ],
      "locationReadings": [
        {
          "lat": 47.615063,
          "long": -122.333551,
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ]
    },
    "unprocessedKeys": {
      "temperatureReadings": [
        {
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ],
      "locationReadings": []
    }
  },
  "error": {
    "type": "DynamoDB:ProvisionedThroughputExceededException",
    "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
  },
  "outErrors": []
}
```

對於內容的幾個注意事項：
+ 調用錯誤已在 `ctx.error` by AWS AppSync 的內容上設定，且錯誤類型已設定為 `DynamoDB:ProvisionedThroughputExceededException`。
+ `ctx.result.data` 即使發生錯誤，也會在 下對應每個資料表的結果。
+ 未處理的金鑰可在 取得`ctx.result.data.unprocessedKeys`。在這裡， AWS AppSync 無法使用金鑰 (sensorId：1，時間戳記：2018-02-01T17：21：05.000\$108：00) 擷取項目，因為資料表輸送量不足。

**注意**  
如果是 `BatchPutItem`，則為 `ctx.result.data.unprocessedItems`。如果是 `BatchDeleteItem`，則為 `ctx.result.data.unprocessedKeys`。

讓我們用三種不同的方式來處理這項錯誤。

#### 1. 抑制呼叫錯誤
<a name="swallowing-the-invocation-error-js"></a>

傳回資料而不處理呼叫錯誤，可有效地抑制錯誤，讓指定 GraphQL 欄位的結果一律成功。

我們編寫的程式碼很熟悉，只著重於結果資料。

**回應處理常式**

```
export function response(ctx) {
  return ctx.result.data
}
```

**GraphQL 回應**

```
{
  "data": {
    "getReadings": [
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "lat": 47.615063,
        "long": -122.333551
      },
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  }
}
```

錯誤回應中不會加入任何錯誤，因為只處理了資料。

#### 2. 引發錯誤以中止回應處理常式執行
<a name="raising-an-error-to-abort-the-response-execution-js"></a>

從用戶端的角度來看，當部分失敗應視為完全失敗時，您可以中止回應處理常式執行，以防止傳回資料。`util.error(...)` 公用程式方法可確實完成這項動作。

**回應處理常式程式碼**

```
export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

**GraphQL 回應**

```
{
  "data": {
    "getReadings": null
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

即使 DynamoDB 批次作業可能已經傳回一些結果，我們還是選擇丟出錯誤，如此 `getReadings` GraphQL 欄位為 null，而錯誤也會加入到 GraphQL 回應的 *errors* (錯誤) 區塊。

#### 3. 附加錯誤，以同時傳回資料和錯誤
<a name="appending-an-error-to-return-both-data-and-errors-js"></a>

在某些情況中，為提供更好的使用者體驗，應用程式可以傳回部分結果，並通知其用戶端有未處理的項目。用戶端可以決定是否要重試，或是轉譯錯誤後傳回給最終使用者。`util.appendError(...)` 是公用程式方法，可讓應用程式設計工具在內容上附加錯誤，而不會干擾回應處理常式的評估，以啟用此行為。評估回應處理常式之後， AWS AppSync 會將任何內容錯誤附加至 GraphQL 回應的錯誤區塊，以處理這些錯誤。

**回應處理常式程式碼**

```
export function response(ctx) {
  if (ctx.error) {
    util.appendError(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

我們轉送 GraphQL 回應錯誤區塊內的調用錯誤和`unprocessedKeys`元素。`getReadings` 欄位也會傳回`locationReadings`資料表中的部分資料，如下方回應所示。

**GraphQL 回應**

```
{
  "data": {
    "getReadings": [
      null,
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

# 使用 HTTP 解析程式 in AWS AppSync
<a name="tutorial-http-resolvers-js"></a>

AWS AppSync 可讓您使用支援的資料來源 （即 Amazon DynamoDB AWS Lambda、Amazon OpenSearch Service 或 Amazon Aurora) 來執行各種操作，以及任何任意 HTTP 端點來解析 GraphQL 欄位。在您的 HTTP 端點可供使用之後，您可以使用資料來源連線到這些端點。然後，您可以在結構描述中設定解析程式以執行 GraphQL 操作，例如查詢、變動和訂閱。本教學將逐步說明一些常見的範例。

在本教學課程中，您將 REST API （使用 Amazon API Gateway 和 Lambda 建立） 與 AWS AppSync GraphQL 端點搭配使用。

## 建立 REST API
<a name="creating-a-rest-api"></a>

您可以使用下列 AWS CloudFormation 範本來設定適用於本教學課程的 REST 端點：

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml)

 AWS CloudFormation 堆疊會執行下列步驟：

1. 設定 Lambda 函數，其中包含您微服務的商業邏輯。

1. 使用下列endpoint/method/content類型組合來設定 API Gateway REST API：


****  

| API 資源路徑 | HTTP 方法 | 已支援的內容類型 | 
| --- | --- | --- | 
|  /v1/users  |  POST  |  application/json  | 
|  /v1/users  |  GET  |  application/json  | 
|  /v1/users/1  |  GET  |  application/json  | 
|  /v1/users/1  |  PUT  |  application/json  | 
|  /v1/users/1  |  DELETE  |  application/json  | 

## 建立 GraphQL API
<a name="creating-your-graphql-api"></a>

若要在 AWS AppSync 中建立 GraphQL API：

1. 開啟 AWS AppSync 主控台，然後選擇**建立 API**。

1. 選擇 **GraphQL APIs**，然後選擇**從頭開始設計**。選擇**下一步**。

1. 針對 API 名稱，輸入 `UserData`。選擇**下一步**。

1. 選擇 `Create GraphQL resources later`。選擇**下一步**。

1. 檢閱您的輸入，然後選擇**建立 API**。

 AWS AppSync 主控台會使用 API 金鑰身分驗證模式為您建立新的 GraphQL API。您可以使用 主控台進一步設定 GraphQL API 並執行請求。

## 建立 GraphQL 結構描述
<a name="creating-a-graphql-schema"></a>

現在您有一個 GraphQL API，讓我們來建立 GraphQL 結構描述吧。在 AWS AppSync 主控台的**結構描述**編輯器中，使用下列程式碼片段：

```
type Mutation {
    addUser(userInput: UserInput!): User
    deleteUser(id: ID!): User
}

type Query {
    getUser(id: ID): User
    listUser: [User!]!
}

type User {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}

input UserInput {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}
```

## 設定您的 HTTP 資料來源
<a name="configure-your-http-data-source"></a>

若要設定您的 HTTP 資料來源，請執行以下作業：

1. 在 your AWS AppSync GraphQL API 的**資料來源**頁面中，選擇**建立資料來源**。

1. 輸入資料來源的名稱，例如 `HTTP_Example`。

1. 在**資料來源類型**中，選擇 **HTTP 端點**。

1. 將端點設定為教學課程開始時建立的 API Gateway 端點。如果您導覽至 Lambda 主控台，並在應用程式下尋找您的**應用程式**，您可以找到堆疊產生的端點。在應用程式的設定中，您應該會看到 **API 端點**，其將是您的端點 AWS AppSync。請確定您不包含階段名稱做為端點的一部分。例如，如果您的端點是 `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com/v1`，您會在 中輸入 `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com`。

**注意**  
目前， AWS AppSync 僅支援公有端點。  
如需 AWS AppSync 服務所辨識之認證授權機構的詳細資訊，請參閱 [AWS AppSync HTTPS 端點的 所辨識的憑證授權機構 (CA)](http-cert-authorities.md#aws-appsync-http-certificate-authorities)。

## 設定解析程式
<a name="configuring-resolvers"></a>

在此步驟中，您將將 HTTP 資料來源連線至 `getUser`和 `addUser`查詢。

若要設定`getUser`解析程式：

1. 在 your AWS AppSync GraphQL API 中，選擇**結構描述**索引標籤。

1. 在**結構描述**編輯器右側，在**解析程式**窗格中和**查詢**類型下，尋找 `getUser` 欄位，然後選擇**連接**。

1. 將解析程式類型保留為 ，`Unit`並將執行時間保留為 `APPSYNC_JS`。

1. 在**資料來源名稱**中，選擇您先前建立的 HTTP 端點。

1. 選擇**建立**。

1. 在**解析程式**程式碼編輯器中，新增下列程式碼片段做為您的請求處理常式：

   ```
   import { util } from '@aws-appsync/utils'
   
   export function request(ctx) {
   	return {
   		version: '2018-05-29',
   		method: 'GET',
   		params: {
   			headers: {
   				'Content-Type': 'application/json',
   			},
   		},
   		resourcePath: `/v1/users/${ctx.args.id}`,
   	}
   }
   ```

1. 新增下列程式碼片段做為您的回應處理常式：

   ```
   export function response(ctx) {
   	const { statusCode, body } = ctx.result
   	// if response is 200, return the response
   	if (statusCode === 200) {
   		return JSON.parse(body)
   	}
   	// if response is not 200, append the response to error block.
   	util.appendError(body, statusCode)
   }
   ```

1. 選擇 **Query (查詢)** 標籤，然後執行以下查詢：

   ```
   query GetUser{
       getUser(id:1){
           id
           username
       }
   }
   ```

   這應該會傳回以下回應：

   ```
   {
       "data": {
           "getUser": {
               "id": "1",
               "username": "nadia"
           }
       }
   }
   ```

若要設定`addUser`解析程式：

1. 選擇 **Schema (結構描述)** 標籤。

1. 在**結構描述**編輯器右側，在**解析程式**窗格中和**查詢**類型下，尋找 `addUser` 欄位，然後選擇**連接**。

1. 將解析程式類型保留為 ，`Unit`並將執行時間保留為 `APPSYNC_JS`。

1. 在**資料來源名稱**中，選擇您先前建立的 HTTP 端點。

1. 選擇**建立**。

1. 在**解析程式**程式碼編輯器中，新增下列程式碼片段做為您的請求處理常式：

   ```
   export function request(ctx) {
       return {
           "version": "2018-05-29",
           "method": "POST",
           "resourcePath": "/v1/users",
           "params":{
               "headers":{
                   "Content-Type": "application/json"
               },
           "body": ctx.args.userInput
           }
       }
   }
   ```

1. 新增下列程式碼片段做為您的回應處理常式：

   ```
   export function response(ctx) {
       if(ctx.error) {
           return util.error(ctx.error.message, ctx.error.type)
       }
       if (ctx.result.statusCode == 200) {
           return ctx.result.body
       } else {
           return util.appendError(ctx.result.body, "ctx.result.statusCode")
       }
   }
   ```

1. 選擇 **Query (查詢)** 標籤，然後執行以下查詢：

   ```
   mutation addUser{
       addUser(userInput:{
           id:"2",
           username:"shaggy"
       }){
           id
           username
       }
   }
   ```

   如果您再次執行`getUser`查詢，應傳回下列回應：

   ```
   {
       "data": {
           "getUser": {
           "id": "2",
           "username": "shaggy"
           }
       }
   }
   ```

## 叫用 AWS 服務
<a name="invoking-aws-services-js"></a>

您可以使用 HTTP 解析程式來設定 AWS 服務的 GraphQL API 介面。對 的 HTTP 請求 AWS 必須使用 [Signature 第 4 版程序](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)簽署，以便 AWS 可以識別傳送這些請求的人員。當您將 IAM 角色與 HTTP 資料來源建立關聯時， AWS AppSync 會代表您計算簽章。

您提供兩個額外的元件，以使用 HTTP 解析程式叫用 AWS 服務：
+ 具有呼叫 AWS 服務 APIs 許可的 IAM 角色
+ 資料來源中的簽署組態

例如，如果您想要使用 HTTP 解析程式呼叫 [ListGraphqlApis 操作](https://docs.aws.amazon.com/appsync/latest/APIReference/API_ListGraphqlApis.html)，請先[建立 AppSync 擔任並連接下列政策的 IAM 角色](attaching-a-data-source.md#aws-appsync-getting-started-build-a-schema-from-scratch)： AWS AppSync 

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "appsync:ListGraphqlApis"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```

------

接著，建立 HTTP 資料來源 for AWS AppSync。在此範例中，您在美國西部 （奧勒岡） 區域呼叫 AWS AppSync。在名為 `http.json` 的檔案中設定以下 HTTP 組態，包含簽署區域和服務名稱：

```
{
    "endpoint": "https://appsync.us-west-2.amazonaws.com/",
    "authorizationConfig": {
        "authorizationType": "AWS_IAM",
        "awsIamConfig": {
            "signingRegion": "us-west-2",
            "signingServiceName": "appsync"
        }
    }
}
```

然後，使用 AWS CLI 建立具有關聯角色的資料來源，如下所示：

```
aws appsync create-data-source --api-id <API-ID> \
                               --name AWSAppSync \
                               --type HTTP \
                               --http-config file:///http.json \
                               --service-role-arn <ROLE-ARN>
```

當您將解析程式連接至結構描述中的 欄位時，請使用下列請求映射範本來 call AWS AppSync：

```
{
    "version": "2018-05-29",
    "method": "GET",
    "resourcePath": "/v1/apis"
}
```

當您對此資料來源執行 GraphQL 查詢時， AWS AppSync 會使用您提供的角色簽署請求，並在請求中包含簽章。查詢會傳回您帳戶中該 AWS 區域的 AWS AppSync GraphQL APIs清單。

# 在 中使用 Aurora PostgreSQL 搭配資料 API AWS AppSync
<a name="aurora-serverless-tutorial-js"></a>

 

了解如何使用 將 GraphQL API 連線至 Aurora PostgreSQL 資料庫 AWS AppSync。此整合可讓您透過 GraphQL 操作執行 SQL 查詢和變動，以建置可擴展的資料驅動型應用程式。 AWS AppSync 提供資料來源，針對使用資料 API 啟用的 Amazon Aurora 叢集執行 SQL 陳述式。您可以使用 AWS AppSync 解析程式，針對具有 GraphQL 查詢、變動和訂閱的資料 API 執行 SQL 陳述式。

開始本教學課程之前，您應該對 AWS 服務和 GraphQL 概念有基本的熟悉。

**注意**  
此教學會使用 `US-EAST-1` 區域。

**Topics**
+ [設定 Aurora PostgreSQL 資料庫](#creating-clusters)
+ [建立資料庫和資料表](#creating-db-table)
+ [建立 GraphQL 結構描述](#rds-graphql-schema)
+ [RDS 的解析程式](#rds-resolvers)
+ [刪除叢集](#rds-delete-cluster)

## 設定 Aurora PostgreSQL 資料庫
<a name="creating-clusters"></a>

將 Amazon RDS 資料來源新增至 之前 AWS AppSync，請執行下列動作。

1. 在 Aurora Serverless v2 叢集上啟用資料 API。

1. 使用 設定秘密 AWS Secrets Manager

1. 使用下列 AWS CLI 命令建立叢集。

   ```
   aws rds create-db-cluster \
               --db-cluster-identifier appsync-tutorial \
               --engine aurora-postgresql \
               --engine-version 16.6 \
               --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \
               --master-username USERNAME \
               --master-user-password COMPLEX_PASSWORD \
               --enable-http-endpoint
   ```

這會傳回叢集的 ARN。建立叢集之後，您必須使用下列 AWS CLI 命令新增 Serverless v2 執行個體。

```
aws rds create-db-instance \
    --db-cluster-identifier appsync-tutorial \
    --db-instance-identifier appsync-tutorial-instance-1 \
    --db-instance-class db.serverless \
    --engine aurora-postgresql
```

**注意**  
這些端點需要一些時間才能啟用。您可以在叢集**的連線與安全**索引標籤中的 RDS 主控台中檢查其狀態。

使用以下 AWS CLI 命令檢查叢集狀態。

```
aws rds describe-db-clusters \
    --db-cluster-identifier appsync-tutorial \
    --query "DBClusters[0].Status"
```

使用 `USERNAME`和上`COMPLEX_PASSWORD`一個步驟中的 ，透過 AWS Secrets Manager 主控台或 AWS CLI 使用輸入檔案建立秘密，如下所示：

```
{
    "username": "USERNAME",
    "password": "COMPLEX_PASSWORD"
}
```

將此做為參數傳遞至 AWS CLI：

```
aws secretsmanager create-secret \
    --name appsync-tutorial-rds-secret \
    --secret-string file://creds.json
```

這會傳回秘密的 ARN。**在主控台中建立資料來源時，請記下** Aurora Serverless v2 叢集和 Secret 的 ARN。 AWS AppSync 

## 建立資料庫和資料表
<a name="creating-db-table"></a>

首先，建立名為 的資料庫`TESTDB`。在 PostgreSQL 中，資料庫是存放資料表和其他 SQL 物件的容器。驗證您的 Aurora Serverless v2 叢集已正確設定，再將其新增至您的 AWS AppSync API。首先，使用 `--sql` 參數建立 *TESTDB* 資料庫，如下所示。

```
aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --sql "create DATABASE \"testdb\"" \
    --database "postgres"
```

 如果執行時沒有任何錯誤，請使用 `create table`命令新增兩個資料表：

```
 aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --database "testdb" \
    --sql 'create table public.todos (id serial constraint todos_pk primary key, description text not null, due date not null, "createdAt" timestamp default now());'

aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --database "testdb" \
    --sql 'create table public.tasks (id serial constraint tasks_pk primary key, description varchar, "todoId" integer not null constraint tasks_todos_id_fk references public.todos);'
```

如果成功，請在 API 中將叢集新增為資料來源。

## 建立 GraphQL 結構描述
<a name="rds-graphql-schema"></a>

現在，您的 Aurora Serverless v2 Data API 正在執行已設定的資料表，我們將建立 GraphQL 結構描述。您可以使用 API 建立精靈從現有資料庫匯入資料表組態，以快速建立 API。

若要開始：

1. 在 AWS AppSync 主控台中，選擇**建立 API**，然後從 **Amazon Aurora 叢集開始**。

1. 指定 API 詳細資訊，例如 **API 名稱**，然後選取您的資料庫以產生 API。

1. 選擇您的資料庫。如有需要，請更新區域，然後選擇您的 Aurora 叢集和 *TESTDB* 資料庫。

1. 選擇您的秘密，然後選擇**匯入**。

1. 探索資料表後，請更新類型名稱。`Todos` 將 變更為 `Todo`，將 `Tasks`變更為 `Task`。

1. 選擇預覽結構描述來**預覽產生的結構描述**。您的結構描述看起來會像這樣：

   ```
   type Todo {
     id: Int!
     description: String!
     due: AWSDate!
     createdAt: String
   }
   
   type Task {
     id: Int!
     todoId: Int!
     description: String
   }
   ```

1. 對於角色，您可以 AWS AppSync 建立新的角色，或使用類似下列的政策來建立角色：

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

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "rds-data:ExecuteStatement"
               ],
               "Resource": [
                   "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial",
                   "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial:*"
               ]
           },
           {
               "Effect": "Allow",
               "Action": [
                   "secretsmanager:GetSecretValue"
               ],
               "Resource": [
               "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret",
               "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret:*"
               ]
           }
       ]
   }
   ```

------

   請注意，您授予角色存取權的政策中有兩個陳述式。第一個資源是您的 Aurora 叢集，第二個資源是您的 AWS Secrets Manager ARN。

   選擇**下一步**，檢閱組態詳細資訊，然後選擇**建立 API**。您現在擁有可完全操作的 API。您可以在**結構描述**頁面上檢閱 API 的完整詳細資訊。

## RDS 的解析程式
<a name="rds-resolvers"></a>

API 建立流程會自動建立解析程式，以與我們的類型互動。如果您查看**結構描述**頁面，您會找到一些混沌的解析程式。
+ `todo` 透過 `Mutation.createTodo` 欄位建立 。
+ `todo` 透過 `Mutation.updateTodo` 欄位更新 。
+ `todo` 透過 `Mutation.deleteTodo` 欄位刪除 。
+ `todo` 透過 `Query.getTodo` 欄位取得單一 。
+ `todos` 透過 `Query.listTodos` 欄位列出所有 。

您可以找到為 `Task`類型連接的類似欄位和解析程式。讓我們進一步了解一些解析程式。

### Mutation.createTodo
<a name="createtodo"></a>

從 AWS AppSync 主控台的結構描述編輯器右側，選擇 `testdb` 旁的 `createTodo(...): Todo`。解析程式程式碼使用 `rds`模組的 `insert`函數，動態建立將資料新增至`todos`資料表的插入陳述式。由於我們使用 Postgres，因此我們可以利用 `returning`陳述式來取回插入的資料。

更新下列解析程式，以正確指定 `due` 欄位的`DATE`類型。

```
import { util } from '@aws-appsync/utils';
import { insert, createPgStatement, toJsonObject, typeHint } from '@aws-appsync/utils/rds';

export function request(ctx) {
    const { input } = ctx.args;
    // if a due date is provided, cast is as `DATE`
    if (input.due) {
        input.due = typeHint.DATE(input.due)
    }
    const insertStatement = insert({
        table: 'todos',
        values: input,
        returning: '*',
    });
    return createPgStatement(insertStatement)
}

export function response(ctx) {
    const { error, result } = ctx;
    if (error) {
        return util.appendError(
            error.message,
            error.type,
            result
        )
    }
    return toJsonObject(result)[0][0]
}
```

儲存解析程式。類型提示會將輸入物件中的 `due` 正確標記為`DATE`類型。這可讓 Postgres 引擎正確解譯值。接著，更新您的結構描述，`id`從`CreateTodo`輸入中移除 。由於 Postgres 資料庫可以傳回產生的 ID，因此您可以依賴它來建立，並以單一請求傳回結果，如下所示。

```
input CreateTodoInput {
    due: AWSDate!
    createdAt: String
    description: String!
}
```

進行變更並更新您的結構描述。前往**查詢**編輯器，將項目新增至資料庫，如下所示。

```
mutation CreateTodo {
  createTodo(input: {description: "Hello World!", due: "2023-12-31"}) {
    id
    due
    description
    createdAt
  }
}
```

您會取得下列結果。

```
{
  "data": {
    "createTodo": {
      "id": 1,
      "due": "2023-12-31",
      "description": "Hello World!",
      "createdAt": "2023-11-14 20:47:11.875428"
    }
  }
}
```

### Query.listTodos
<a name="listtodo"></a>

從主控台的結構描述編輯器右側，選擇 `testdb` 旁的 `listTodos(id: ID!): Todo`。請求處理常式會使用選取公用程式函數，在執行時間動態建置請求。

```
export function request(ctx) {
    const { filter = {}, limit = 100, nextToken } = ctx.args;
    const offset = nextToken ? +util.base64Decode(nextToken) : 0;
    const statement = select({
        table: 'todos',
        columns: '*',
        limit,
        offset,
        where: filter,
    });
    return createPgStatement(statement)
}
```

我們希望`todos`根據`due`日期進行篩選。讓我們更新解析程式，將`due`值轉換為 `DATE`。更新匯入和請求處理常式的清單，如下所示。

```
import { util } from '@aws-appsync/utils';
import * as rds from '@aws-appsync/utils/rds';

export function request(ctx) {
  const { filter: where = {}, limit = 100, nextToken } = ctx.args;
  const offset = nextToken ? +util.base64Decode(nextToken) : 0;

  // if `due` is used in a filter, CAST the values to DATE.
  if (where.due) {
    Object.entries(where.due).forEach(([k, v]) => {
      if (k === 'between') {
        where.due[k] = v.map((d) => rds.typeHint.DATE(d));
      } else {
        where.due[k] = rds.typeHint.DATE(v);
      }
    });
  }

  const statement = rds.select({
    table: 'todos',
    columns: '*',
    limit,
    offset,
    where,
  });
  return rds.createPgStatement(statement);
}

export function response(ctx) {
  const {
    args: { limit = 100, nextToken },
    error,
    result,
  } = ctx;
  if (error) {
    return util.appendError(error.message, error.type, result);
  }
  const offset = nextToken ? +util.base64Decode(nextToken) : 0;
  const items = rds.toJsonObject(result)[0];
  const endOfResults = items?.length < limit;
  const token = endOfResults ? null : util.base64Encode(`${offset + limit}`);
  return { items, nextToken: token };
}
```

在**查詢**編輯器中執行下列動作。

```
query LIST {
  listTodos(limit: 10, filter: {due: {between: ["2021-01-01", "2025-01-02"]}}) {
    items {
      id
      due
      description
    }
  }
}
```

### Mutation.updateTodo
<a name="updatetodo"></a>

您也可以`update`使用 `Todo`。從**查詢**編輯器，讓我們更新第一個 `id` `Todo`項目`1`。

```
mutation UPDATE {
  updateTodo(input: {id: 1, description: "edits"}) {
    description
    due
    id
  }
}
```

請注意，您必須指定要更新之項目`id`的 。您也可以指定條件，只更新符合特定條件的項目。例如，如果描述以 開頭，我們可能只想要編輯項目`edits`，如下所示。

```
mutation UPDATE {
  updateTodo(input: {id: 1, description: "edits: make a change"}, condition: {description: {beginsWith: "edits"}}) {
    description
    due
    id
  }
}
```

就像我們處理 `create`和 `list`操作的方式一樣，我們可以更新解析程式，將 `due` 欄位轉換為 `DATE`。將這些變更儲存至 `updateTodo`，如下所示。

```
import { util } from '@aws-appsync/utils';
import * as rds from '@aws-appsync/utils/rds';

export function request(ctx) {
  const { input: { id, ...values }, condition = {}, } = ctx.args;
  const where = { ...condition, id: { eq: id } };

  // if `due` is used in a condition, CAST the values to DATE.
  if (condition.due) {
    Object.entries(condition.due).forEach(([k, v]) => {
      if (k === 'between') {
        condition.due[k] = v.map((d) => rds.typeHint.DATE(d));
      } else {
        condition.due[k] = rds.typeHint.DATE(v);
      }
    });
  }

  // if a due date is provided, cast is as `DATE`
  if (values.due) {
    values.due = rds.typeHint.DATE(values.due);
  }

  const updateStatement = rds.update({
    table: 'todos',
    values,
    where,
    returning: '*',
  });
  return rds.createPgStatement(updateStatement);
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    return util.appendError(error.message, error.type, result);
  }
  return rds.toJsonObject(result)[0][0];
}
```

現在請嘗試使用 條件進行更新：

```
mutation UPDATE {
  updateTodo(
    input: {
        id: 1, description: "edits: make a change", due: "2023-12-12"},
    condition: {
        description: {beginsWith: "edits"}, due: {ge: "2023-11-08"}})
    {
          description
          due
          id
        }
}
```

### Mutation.deleteTodo
<a name="deletetodo"></a>

您可以`Todo`具有 `deleteTodo` 變動的 `delete` 。這的運作方式與`updateTodo`變動類似，而且您必須指定您要刪除之項目`id`的 ，如下所示。

```
mutation DELETE {
  deleteTodo(input: {id: 1}) {
    description
    due
    id
  }
}
```

### 撰寫自訂查詢
<a name="writing-custom-queries"></a>

我們已使用`rds`模組公用程式來建立 SQL 陳述式。我們也可以撰寫自己的自訂靜態陳述式來與資料庫互動。首先，更新結構描述以從`CreateTask`輸入中移除 `id` 欄位。

```
input CreateTaskInput {
    todoId: Int!
    description: String
}
```

接著，建立幾個任務。任務與 有外部金鑰關係`Todo`，如下所示。

```
mutation TASKS {
  a: createTask(input: {todoId: 2, description: "my first sub task"}) { id }
  b:createTask(input: {todoId: 2, description: "another sub task"}) { id }
  c: createTask(input: {todoId: 2, description: "a final sub task"}) { id }
}
```

在名為 的`Query`類型中建立新的欄位`getTodoAndTasks`，如下所示。

```
getTodoAndTasks(id: Int!): Todo
```

將`tasks`欄位新增至 `Todo`類型，如下所示。

```
type Todo {
    due: AWSDate!
    id: Int!
    createdAt: String
    description: String!
    tasks:TaskConnection
}
```

儲存結構描述。從主控台的結構描述編輯器右側，選擇**連接 解析程式**`getTodosAndTasks(id: Int!): Todo`。選擇您的 Amazon RDS 資料來源。使用下列程式碼更新您的解析程式。

```
import { sql, createPgStatement,toJsonObject } from '@aws-appsync/utils/rds';

export function request(ctx) {
    return createPgStatement(
        sql`SELECT * from todos where id = ${ctx.args.id}`,
        sql`SELECT * from tasks where "todoId" = ${ctx.args.id}`);
}

export function response(ctx) {
    const result = toJsonObject(ctx.result);
    const todo = result[0][0];
    if (!todo) {
        return null;
    }
    todo.tasks = { items: result[1] };
    return todo;
}
```

在此程式碼中，我們使用`sql`標籤範本來撰寫 SQL 陳述式，以便在執行時間安全地將動態值傳遞給 。 一次最多`createPgStatement`可能需要兩個 SQL 請求。我們使用它為 傳送一個查詢`todo`，為 傳送另一個查詢`tasks`。您可以透過`JOIN`陳述式或該事項的任何其他方法完成此操作。該想法能夠撰寫您自己的 SQL 陳述式來實作您的商業邏輯。若要在**查詢**編輯器中使用查詢，請執行下列動作。

```
query TodoAndTasks {
  getTodosAndTasks(id: 2) {
    id
    due
    description
    tasks {
      items {
        id
        description
      }
    }
  }
}
```

## 刪除叢集
<a name="rds-delete-cluster"></a>

**重要**  
刪除叢集是永久的。在執行此動作之前，請徹底檢閱您的專案。

若要刪除叢集：

```
$ aws rds delete-db-cluster \
    --db-cluster-identifier appsync-tutorial \
    --skip-final-snapshot
```