

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

# 的 VTL 解析程式教學課程 AWS AppSync
<a name="tutorials"></a>

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

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

AWS AppSync 使用解析程式以 Apache Velocity 範本語言 (VTL) 編寫的*映射*範本。如需使用映射範本的詳細資訊，請參閱[解析程式映射範本參考](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference)。如需使用 VTL 的詳細資訊，請參閱[解析程式映射範本程式設計指南](resolver-mapping-template-reference-programming-guide.md#aws-appsync-resolver-mapping-template-reference-programming-guide)。

AWS AppSync 支援從 GraphQL 結構描述自動佈建 DynamoDB 資料表，如從結構描述佈建 （選用） 和啟動範例結構描述中所述。您也可以從現有將建立結構描述和連接解析程式的 DynamoDB 資料表滙入。這在從 Amazon DynamoDB 匯入中概述 （選用）。

**Topics**
+ [使用 DynamoDB 解析程式建立簡單的貼文應用程式](tutorial-dynamodb-resolvers.md)
+ [使用 AWS Lambda 解析程式](tutorial-lambda-resolvers.md)
+ [使用 OpenSearch Service 解析程式](tutorial-elasticsearch-resolvers.md)
+ [使用本機解析程式](tutorial-local-resolvers.md)
+ [結合 GraphQL 解析程式](tutorial-combining-graphql-resolvers.md)
+ [使用 DynamoDB 批次操作](tutorial-dynamodb-batch.md)
+ [執行 DynamoDB 交易](tutorial-dynamodb-transact.md)
+ [使用 HTTP 解析程式](tutorial-http-resolvers.md)
+ [使用 Aurora Serverless v2 解析程式](tutorial-rds-resolvers.md)
+ [使用管道解析程式](tutorial-pipeline-resolvers.md)
+ [在版本控制的資料來源上使用 Delta Sync 操作](tutorial-delta-sync.md)

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

本教學課程說明如何將自己的 Amazon DynamoDB 資料表帶到 AWS AppSync，並將其連接到 GraphQL API。

您可以讓 AWS AppSync 代表您佈建 DynamoDB 資源。或者，如果您願意，您可以透過建立資料來源和解析程式來將現有的資料表連接到 GraphQL 結構描述。在這兩種情況下，您將能夠透過 GraphQL 陳述式讀取和寫入 DynamoDB 資料庫，以及訂閱即時資料。

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

## 設定 DynamoDB 資料表
<a name="setting-up-your-ddb-tables"></a>

若要開始本教學課程，您必須先遵循下列步驟來佈建 AWS 資源。

1. 在 CLI 中使用下列 AWS CloudFormation 範本佈建 AWS 資源：

   ```
   aws cloudformation create-stack \
       --stack-name AWSAppSyncTutorialForAmazonDynamoDB \
       --template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml \
       --capabilities CAPABILITY_NAMED_IAM
   ```

   或者，您可以在帳戶中 AWS 的美國西部 2 （奧勒岡） 區域啟動下列 CloudFormation 堆疊。

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

   這將會建立下列項目：
   + 稱為 `AppSyncTutorial-Post`的 DynamoDB 資料表，可存放`Post`資料。
   + IAM 角色和相關聯的 IAM 受管政策，以允許 AWS AppSync 與`Post`資料表互動。

1. 要查看有關堆疊和建立的資源的詳細資訊，請執行以下 CLI 命令：

   ```
   aws cloudformation describe-stacks --stack-name AWSAppSyncTutorialForAmazonDynamoDB
   ```

1. 之後若要刪除資源，您可以執行以下項目：

   ```
   aws cloudformation delete-stack --stack-name AWSAppSyncTutorialForAmazonDynamoDB
   ```

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

若要在 AWS AppSync 中建立 GraphQL API：

1. 登入 AWS 管理主控台 並開啟 [AppSync 主控台](https://console.aws.amazon.com/appsync/)。

   1. 在 **APIs儀表板**中，選擇**建立 API**。

1. 在**自訂您的 API 或從 Amazon DynamoDB 匯入**視窗下，選擇**從頭建置**。

   1. 選擇相同視窗右側的**開始**。

1. 在 **API 名稱**欄位中，將 API 的名稱設定為 `AWSAppSyncTutorial`。

1. 選擇**建立**。

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

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

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

1. 登入 AWS 管理主控台 並開啟 [AppSync 主控台](https://console.aws.amazon.com/appsync/)。

   1. 在 **APIs儀表板**中，選擇您剛建立的 API。

1. 在**側邊欄中**，選擇**結構描述**。

   1. 在**結構描述**窗格中，使用下列程式碼取代內容：

     ```
     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. 選擇**儲存**。

這個結構描述會定義 `Post` 類型和操作以新增和取得 `Post` 物件。

## 設定 DynamoDB 資料表的資料來源
<a name="configuring-the-data-source-for-the-ddb-tables"></a>

接著，將結構描述中定義的查詢和變動連結至 `AppSyncTutorial-Post`DynamoDB 資料表。

首先， AWS AppSync 需要注意您的資料表。您可以在 AWS AppSync 中設定資料來源來執行此操作：

1. 登入 AWS 管理主控台 並開啟 [AppSync 主控台](https://console.aws.amazon.com/appsync/)。

   1. 在 **APIs儀表板**中，選擇您的 GraphQL API。

   1. 在**側邊欄中**，選擇**資料來源**。

1. 選擇 **Create data source (建立資料來源)**。

   1. 對於**資料來源名稱**，在 中輸入 `PostDynamoDBTable`。

   1. 針對**資料來源類型**，選擇 **Amazon DynamoDB 資料表**。

   1. 針對**區域**，選擇 **US-WEST-2**。

   1. 針對**資料表名稱**，選擇 **AppSyncTutorial-Post** DynamoDB 資料表。

   1. 建立新的 IAM 角色 （建議） 或選擇具有 IAM `lambda:invokeFunction` 許可的現有角色。現有角色需要信任政策，如[連接資料來源](attaching-a-data-source.md)一節所述。

      以下是 IAM 政策範例，其具有對資源執行操作所需的許可：

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

****  

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

------

1. 選擇**建立**。

## 設定 addPost 解析程式 (DynamoDB PutItem)
<a name="setting-up-the-addpost-resolver-dynamodb-putitem"></a>

After AWS AppSync 知道 DynamoDB 資料表，您可以透過定義**解析程式**將其連結至個別查詢和變動。您建立的第一個解析程式是`addPost`解析程式，可讓您在 `AppSyncTutorial-Post` DynamoDB 資料表中建立文章。

解析程式具有下列元件：
+ 在 GraphQL 結構描述中要附加解析程式的位置。在這種情況下，您會對 `addPost` 類型上的 `Mutation` 欄位設定解析程式。發起人呼叫 `mutation { addPost(...){...} }` 時會叫用此解析程式。
+ 用於此解析程式的資料來源。在這種情況下，您想要使用先前定義的 `PostDynamoDBTable` 資料來源，如此您可以將項目新增到 `AppSyncTutorial-Post` DynamoDB 資料表。
+ 請求映射範本。請求映射範本的目的是從發起人取得傳入請求，並將其轉換為指示 for AWS AppSync 以對 DynamoDB 執行。
+ 回應映射範本。回應映射範本的任務即是從 DynamoDB 取得回應，並將其轉譯為 GraphQL 期望的項目。如果 DynamoDB 中的資料形狀與 GraphQL 中的 `Post` 類型不同，此功能會很有用，但如果他們的形狀一樣，您只需傳遞資料。

設定解析程式：

1. 登入 AWS 管理主控台 並開啟 [AppSync 主控台](https://console.aws.amazon.com/appsync/)。

   1. 在 **APIs儀表板**中，選擇您的 GraphQL API。

   1. 在**側邊欄中**，選擇**資料來源**。

1. 選擇 **Create data source (建立資料來源)**。

   1. 對於**資料來源名稱**，在 中輸入 `PostDynamoDBTable`。

   1. 針對**資料來源類型**，選擇 **Amazon DynamoDB 資料表**。

   1. 針對**區域**，選擇 **US-WEST-2**。

   1. 針對**資料表名稱**，選擇 **AppSyncTutorial-Post** DynamoDB 資料表。

   1. 建立新的 IAM 角色 （建議） 或選擇具有 IAM `lambda:invokeFunction` 許可的現有角色。現有角色需要信任政策，如[連接資料來源](attaching-a-data-source.md)一節所述。

      以下是 IAM 政策範例，其具有對資源執行操作所需的許可：

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

****  

      ```
      { 
           "Version":"2012-10-17",		 	 	  
           "Statement": [ 
               { 
                   "Effect": "Allow", 
                   "Action": [ "lambda:invokeFunction" ], 
                   "Resource": [ 
                       "arn:aws:lambda:us-west-2:123456789012:function:myFunction", 
                       "arn:aws:lambda:us-west-2:123456789012:function:myFunction:*" 
                   ] 
               } 
           ] 
       }
      ```

------

1. 選擇**建立**。

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

1. 在右側的**資料類型**窗格中，尋找**變動**類型的 **addPost** 欄位，然後選擇**連接**。

1. 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。

1. 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。

1. 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

   ```
   {
       "version" : "2017-02-28",
       "operation" : "PutItem",
       "key" : {
           "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
       },
       "attributeValues" : {
           "author" : $util.dynamodb.toDynamoDBJson($context.arguments.author),
           "title" : $util.dynamodb.toDynamoDBJson($context.arguments.title),
           "content" : $util.dynamodb.toDynamoDBJson($context.arguments.content),
           "url" : $util.dynamodb.toDynamoDBJson($context.arguments.url),
           "ups" : { "N" : 1 },
           "downs" : { "N" : 0 },
           "version" : { "N" : 1 }
       }
   }
   ```

   **注意：**所有金鑰和屬性值皆有指定的*類型*。例如，您將 `author` 欄位設為 `{ "S" : "${context.arguments.author}" }`。`S` 部分指出 to AWS AppSync 和 DynamoDB 的值將是字串值。會將實際值填入 `author` 引數。同樣地，`version` 欄位是號碼欄位，因為它會將 `N` 用於類型。最後，您也可以初始化 `ups`、`downs` 和 `version` 欄位。

   在本教學課程中，您已指定 GraphQL `ID!`類型，該類型會將插入 DynamoDB 的新項目編製索引，做為 client arguments. AWS AppSync comes 的一部分，其中包含名為 的自動 ID 產生公用程式`$utils.autoId()`，您也可以以 的形式使用`"id" : { "S" : "${$utils.autoId()}" }`。然後，您可以直接將 `id: ID!` 移出 `addPost()` 的結構描述定義，該 ID 便會自動插入。您不會在本教學課程中使用此技術，但您應該將其視為寫入 DynamoDB 資料表時的最佳實務。

   如需映射範本的詳細資訊，請參閱[解析程式映射範本概觀](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)參考文件。如需 GetItem 要求映射的詳細資訊，請參閱 [GetItem](aws-appsync-resolver-mapping-template-reference-dynamodb-getitem.md) 參考文件。如需有關類型的詳細資訊，請參閱[類型系統 (要求映射)](aws-appsync-resolver-mapping-template-reference-dynamodb-typed-values-request.md) 參考文件。

1. 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

   ```
   $utils.toJson($context.result)
   ```

    **注意：**由於 `AppSyncTutorial-Post` 資料表中的資料形狀正好與 GraphQL 中的 `Post` 類型形狀相符，回應映射範本只需直接傳遞結果。另請注意，此教學課程中的所有範例會使用相同的回應映射範本，因此您只需建立一個檔案。

1. 選擇**儲存**。

### 呼叫 API 以新增文章
<a name="call-the-api-to-add-a-post"></a>

現在已設定解析程式， AWS AppSync 可以將傳入`addPost`的變動轉譯為 DynamoDB PutItem 操作。您現在可以執行變動以將項目放置到資料表中。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格：

  ```
  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
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 新建立文章的結果應該會出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

  ```
  {
    "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
      }
    }
  }
  ```

以下是發生的情況：
+ AWS AppSync 收到`addPost`變動請求。
+ AWS AppSync 接受了請求和請求映射範本，並產生了請求映射文件。此看起來如下：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "PutItem",
      "key" : {
          "id" : { "S" : "123" }
      },
      "attributeValues" : {
          "author": { "S" : "AUTHORNAME" },
          "title": { "S" : "Our first post!" },
          "content": { "S" : "This is our first post." },
          "url": { "S" : "https://aws.amazon.com/appsync/" },
          "ups" : { "N" : 1 },
          "downs" : { "N" : 0 },
          "version" : { "N" : 1 }
      }
  }
  ```
+ AWS AppSync 使用請求映射文件來產生和執行 DynamoDB`PutItem` 請求。
+ 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
  }
  ```
+ 透過回應映射文件來進行傳遞，這可讓其以原狀進行傳遞。
+ 傳回在 GraphQL 回應中新建立的物件。

## 設定 getPost 解析程式 (DynamoDB GetItem)
<a name="setting-up-the-getpost-resolver-ddb-getitem"></a>

現在，您可以將資料新增至 `AppSyncTutorial-Post`DynamoDB 資料表，您需要設定`getPost`查詢，以便從`AppSyncTutorial-Post`資料表擷取該資料。若要執行此作業，您可以設定另一個解析程式。
+ 選擇 **Schema (結構描述)** 標籤。
+ 在右側的**資料類型**窗格中，尋找**查詢**類型的 **getPost** 欄位，然後選擇**連接**。
+ 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
      }
  }
  ```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```
+ 選擇**儲存**。

### 呼叫 API 以取得文章
<a name="call-the-api-to-get-a-post"></a>

現在已設定解析程式， AWS AppSync 知道如何將傳入`getPost`查詢轉譯為 DynamoDB`GetItem` 操作。您現在可以執行查詢，擷取您稍早建立的貼文。
+ 選擇 **Queries (查詢)** 標籤。
+ 在 **Queries (查詢)** 窗格中，貼入下面內容：

  ```
  query getPost {
    getPost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 從 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
      }
    }
  }
  ```

以下是發生的情況：
+ AWS AppSync 收到`getPost`查詢請求。
+ AWS AppSync 接受了請求和請求映射範本，並產生了請求映射文件。此看起來如下：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : { "S" : "123" }
      }
  }
  ```
+ AWS AppSync 使用請求映射文件來產生和執行 DynamoDB GetItem 請求。
+ AWS AppSync 取得`GetItem`請求的結果，並將其轉換回 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
  }
  ```
+ 透過回應映射文件來進行傳遞，這可讓其以原狀進行傳遞。
+ 在回應中傳回擷取的物件。

或者，請採用下列範例：

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

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

```
{
    "version" : "2017-02-28",
    "operation" : "GetItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "projection" : {
     "expression" : "#author, id, title",
     "expressionNames" : { "#author" : "author"}
    }
}
```

## 建立 updatePost Mutation (DynamoDB UpdateItem)
<a name="create-an-updatepost-mutation-ddb-updateitem"></a>

到目前為止，您可以在 DynamoDB 中建立和擷取`Post`物件。接下來，您會設定新的變動，讓我們能夠更新物件。您可以使用 UpdateItem DynamoDB 操作來執行此作業。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中的 `Mutation` 類型來新增 `updatePost` 變動，如下所示：

  ```
  type Mutation {
      updatePost(
          id: ID!,
          author: String!,
          title: String!,
          content: String!,
          url: String!
      ): Post
      addPost(
          author: String!
          title: String!
          content: String!
          url: String!
      ): Post!
  }
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立的 **updatePost** 欄位，然後選擇**連接**。
+ 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "SET author = :author, title = :title, content = :content, #url = :url ADD version :one",
          "expressionNames": {
              "#url" : "url"
          },
          "expressionValues": {
              ":author" : $util.dynamodb.toDynamoDBJson($context.arguments.author),
              ":title" : $util.dynamodb.toDynamoDBJson($context.arguments.title),
              ":content" : $util.dynamodb.toDynamoDBJson($context.arguments.content),
              ":url" : $util.dynamodb.toDynamoDBJson($context.arguments.url),
              ":one" : { "N": 1 }
          }
      }
  }
  ```

   **注意：**此解析程式正在使用 DynamoDB UpdateItem，這與 PutItem 操作明顯不同。您只是要求 DynamoDB 更新特定屬性，而不是撰寫整個項目。這是使用 DynamoDB Update Expressions 完成。表達式本身會在 `expression` 區段中的 `update` 欄位中指定。其要求設定 `author`、`title`、`content` 以及 url 屬性，然後遞增 `version` 欄位。要使用的值不會出現在表達式本身；表達式的預留位置名稱開頭為冒號，其會在 `expressionValues` 欄位中加以定義。最後，DynamoDB 保留了無法在 中顯示的單字`expression`。例如，由於 `url` 是保留字，因此若要更新 `url` 欄位，您可以使用名稱預留位置，並在 `expressionNames` 欄位中定義他們。

  如需 `UpdateItem` 請求映射的詳細資訊，請參閱 [UpdateItem](aws-appsync-resolver-mapping-template-reference-dynamodb-updateitem.md) 參考文件。如需有關如何撰寫更新表達式的詳細資訊，請參閱 [DynamoDB UpdateExpressions 文件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)。
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```

### 呼叫 API 以更新文章
<a name="call-the-api-to-update-a-post"></a>

現在已設定解析程式， AWS AppSync 知道如何將傳入`update`變動轉換為 DynamoDB`Update` 操作。您現在可以執行變動，以更新您之前撰寫的項目。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation updatePost {
    updatePost(
      id:"123"
      author: "A new author"
      title: "An updated author!"
      content: "Now with updated content!"
      url: "https://aws.amazon.com/appsync/"
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ DynamoDB 中更新的文章應該會出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

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

在此範例中， `ups`和 `downs` 欄位並未修改，因為請求映射範本並未 ask AWS AppSync 和 DynamoDB 對這些欄位執行任何動作。此外， `version` 欄位以 1 遞增，因為您要求 AWS AppSync 和 DynamoDB 將 1 新增至 `version` 欄位。

## 修改 updatePost 解析程式 (DynamoDB UpdateItem)
<a name="modifying-the-updatepost-resolver-dynamodb-updateitem"></a>

這對於 `updatePost` 變動而言是一個好的開始，但有兩個主要的問題：
+ 如果您只想更新單一欄位，您必須更新所有欄位。
+ 如果兩個人同時修改物件，您便可能會失去資訊。

為了解決這些問題，您將會修改 `updatePost` 變動，使其僅修改請求中指定的引數，然後將條件新增到 `UpdateItem` 操作。

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

1. 在 **Schema (結構描述)** 窗格中，修改 `Mutation` 類型中的 `updatePost` 欄位，以便從 `author`、`title`、`content` 及 `url` 引數中移除驚嘆號，並請確保 `id` 欄位維持原狀。這會使它們變成可選引數。此外，新增必要 `expectedVersion` 引數。

   ```
   type Mutation {
       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. 選擇**儲存**。

1. 在右側的**資料類型**窗格中，尋找 **Mutation** 類型的 **updatePost** 欄位。

1. 選擇 **PostDynamoDBTable** 以開啟現有的解析程式。

1. 在 **Configure the request mapping template (設定要求映射範本)** 中修改要求映射範本，如下所示：

   ```
   {
       "version" : "2017-02-28",
       "operation" : "UpdateItem",
       "key" : {
           "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
       },
   
       ## Set up some space to keep track of things you're updating **
       #set( $expNames  = {} )
       #set( $expValues = {} )
       #set( $expSet = {} )
       #set( $expAdd = {} )
       #set( $expRemove = [] )
   
       ## Increment "version" by 1 **
       $!{expAdd.put("version", ":one")}
       $!{expValues.put(":one", { "N" : 1 })}
   
       ## Iterate through each argument, skipping "id" and "expectedVersion" **
       #foreach( $entry in $context.arguments.entrySet() )
           #if( $entry.key != "id" && $entry.key != "expectedVersion" )
               #if( (!$entry.value) && ("$!{entry.value}" == "") )
                   ## If the argument is set to "null", then remove that attribute from the item in DynamoDB **
   
                   #set( $discard = ${expRemove.add("#${entry.key}")} )
                   $!{expNames.put("#${entry.key}", "$entry.key")}
               #else
                   ## Otherwise set (or update) the attribute on the item in DynamoDB **
   
                   $!{expSet.put("#${entry.key}", ":${entry.key}")}
                   $!{expNames.put("#${entry.key}", "$entry.key")}
                   $!{expValues.put(":${entry.key}", { "S" : "${entry.value}" })}
               #end
           #end
       #end
   
       ## Start building the update expression, starting with attributes you're going to SET **
       #set( $expression = "" )
       #if( !${expSet.isEmpty()} )
           #set( $expression = "SET" )
           #foreach( $entry in $expSet.entrySet() )
               #set( $expression = "${expression} ${entry.key} = ${entry.value}" )
               #if ( $foreach.hasNext )
                   #set( $expression = "${expression}," )
               #end
           #end
       #end
   
       ## Continue building the update expression, adding attributes you're going to ADD **
       #if( !${expAdd.isEmpty()} )
           #set( $expression = "${expression} ADD" )
           #foreach( $entry in $expAdd.entrySet() )
               #set( $expression = "${expression} ${entry.key} ${entry.value}" )
               #if ( $foreach.hasNext )
                   #set( $expression = "${expression}," )
               #end
           #end
       #end
   
       ## Continue building the update expression, adding attributes you're going to REMOVE **
       #if( !${expRemove.isEmpty()} )
           #set( $expression = "${expression} REMOVE" )
   
           #foreach( $entry in $expRemove )
               #set( $expression = "${expression} ${entry}" )
               #if ( $foreach.hasNext )
                   #set( $expression = "${expression}," )
               #end
           #end
       #end
   
       ## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
       "update" : {
           "expression" : "${expression}"
           #if( !${expNames.isEmpty()} )
               ,"expressionNames" : $utils.toJson($expNames)
           #end
           #if( !${expValues.isEmpty()} )
               ,"expressionValues" : $utils.toJson($expValues)
           #end
       },
   
       "condition" : {
           "expression"       : "version = :expectedVersion",
           "expressionValues" : {
               ":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion)
           }
       }
   }
   ```

1. 選擇**儲存**。

此範本是更為複雜的範例。它示範映射範本的強大功能和靈活性。它會循環使用所有引數，但會略過 `id` 和 `expectedVersion`。如果引數設為某個項目，則其 ask AWS AppSync 和 DynamoDB 會更新 DynamoDB 中物件上的該屬性。如果屬性設定為 null，則其 asked AWS AppSync 和 DynamoDB 會從後置物件中移除該屬性。若未指定引數，它將會保留該屬性。它會增量 `version` 欄位。

另外，還有新的 `condition` 區段。條件表達式可讓您 tell AWS AppSync 和 DynamoDB，根據在執行操作之前已在 DynamoDB 中的物件狀態，請求是否應成功。在此情況下，只有在 DynamoDB 中目前項目`version`的欄位完全符合`expectedVersion`引數時，您才希望`UpdateItem`請求成功。

如需條件表達式的詳細資訊，請參閱[條件表達式](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md)參考文件。

### 呼叫 API 以更新文章
<a name="id1"></a>

讓我們嘗試使用新解析程式來更新 `Post` 物件：
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation updatePost {
    updatePost(
      id:123
      title: "An empty story"
      content: null
      expectedVersion: 2
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 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": 3
      }
    }
  }
  ```

在此請求中，您要求 AWS AppSync 和 DynamoDB 僅更新 `title`和 `content` 欄位。它會將所有其他欄位單獨保留 (除了遞增 `version` 欄位)。您將 `title` 屬性設定為新的值，並從文章中移除 `content` 屬性。`author`、`url`、`ups` 和 `downs` 欄位維持原狀。

嘗試再次執行變動要求，讓要求維持原狀。您應該會看到類似以下的回應：

```
{
  "data": {
    "updatePost": null
  },
  "errors": [
    {
      "path": [
        "updatePost"
      ],
      "data": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 3
      },
      "errorType": "DynamoDB:ConditionalCheckFailedException",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)"
    }
  ]
}
```

條件表達式評估為「false」，因此要求失敗：
+ 第一次執行請求時，DynamoDB 中文章的 `version` 欄位值為 `2`，符合 `expectedVersion`引數。請求成功，這表示 `version` 欄位已在 DynamoDB 中遞增至 `3`。
+ 第二次執行請求時，DynamoDB 中文章的 `version` 欄位值為 `3`，這不符合引`expectedVersion`數。

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

an AWS AppSync DynamoDB 解析程式的一項功能是傳回 DynamoDB 中貼文物件的目前值。您可以在 GraphQL 回應的 `data` 區段 `errors` 欄位中找到此值。您的應用程式可以使用這些資訊來決定繼續執行的方式。在這種情況下，您可以看到 DynamoDB 中物件`version`的欄位設定為 `3`，因此您只要將`expectedVersion`引數更新為 `3`，請求就會再次成功。

如需有關處理條件檢查錯誤的詳細資訊，請參閱[條件表達式](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md)映射範本參考文件。

## 建立 upvotePost 和 downvotePost 變動 (DynamoDB UpdateItem)
<a name="create-upvotepost-and-downvotepost-mutations-ddb-updateitem"></a>

`Post` 類型具有 `ups` 和 `downs` 欄位，可記錄贊同數和不贊同數，但是到目前為止，API 不會讓我們使用它們來執行任何動作。讓我們新增一些變動來對文章表示贊同與不贊同。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中的 `Mutation` 類型以新增 `upvotePost` 和 `downvotePost` 變動，如下所示：

  ```
  type Mutation {
      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!
  }
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立的 **upvotePost** 欄位，然後選擇**連接**。
+ 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "ADD ups :plusOne, version :plusOne",
          "expressionValues" : {
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立`downvotePost`的欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "ADD downs :plusOne, version :plusOne",
          "expressionValues" : {
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```
+ 選擇**儲存**。

### 呼叫 API 來對文章表達贊同或不贊同
<a name="call-the-api-to-upvote-and-downvote-a-post"></a>

現在已設定新的解析程式， AWS AppSync 知道如何將傳入`upvotePost`或`downvote`變動轉換為 DynamoDB UpdateItem 操作。您現在可以執行變動，對您之前建立的文章表達贊同或不贊同。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation votePost {
    upvotePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 文章會在 DynamoDB 中更新，並應出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

  ```
  {
    "data": {
      "upvotePost": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 6,
        "downs": 0,
        "version": 4
      }
    }
  }
  ```
+ 再選擇幾次 **Execute query (執行查詢)**。您應查看在每次執行查詢時以 1 為單位而遞增的 `ups` 和 `version` 欄位。
+ 變更查詢以呼叫 `downvotePost` 變動，如下所示：

  ```
  mutation votePost {
    downvotePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。此時，您應查看在每次執行查詢時以 1 為單位而遞增的 `downs` 和 `version` 欄位。

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

## 設定 deletePost Resolver (DynamoDB DeleteItem)
<a name="setting-up-the-deletepost-resolver-ddb-deletepost"></a>

您要設定的下一個變動是刪除貼文。您將使用 `DeleteItem` DynamoDB 操作來執行此操作。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中的 `Mutation` 類型來新增 `deletePost` 變動，如下所示：

  ```
  type Mutation {
      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!
  }
  ```

  這次您將 `expectedVersion` 欄位設為選用，稍後在您新增要求映射範本時，將對此進行說明。
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立的**刪除**欄位，然後選擇**連接**。
+ 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "DeleteItem",
      "key": {
          "id": $util.dynamodb.toDynamoDBJson($context.arguments.id)
      }
      #if( $context.arguments.containsKey("expectedVersion") )
          ,"condition" : {
              "expression"       : "attribute_not_exists(id) OR version = :expectedVersion",
              "expressionValues" : {
                  ":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion)
              }
          }
      #end
  }
  ```

   **注意：**`expectedVersion` 引數為可選引數。如果發起人在請求中設定`expectedVersion`引數，則範本會新增一個條件，只有在項目已刪除或 DynamoDB 中的文章`version`屬性完全符合 時，才會允許`DeleteItem`請求成功`expectedVersion`。如果省略，將不會在 `DeleteItem` 要求中指定條件表達式。無論 的值為何`version`，或 DynamoDB 中是否存在該項目，它都會成功。
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```

   **注意：**即使您正在刪除某個項目，如果該項目尚未刪除，您仍然可以傳回已刪除的項目。
+ 選擇**儲存**。

如需有關 `DeleteItem` 要求映射的詳細資訊，請參閱 [DeleteItem](aws-appsync-resolver-mapping-template-reference-dynamodb-deleteitem.md) 參考文件。

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

現在已設定解析程式， AWS AppSync 知道如何將傳入`delete`變動轉換為 DynamoDB`DeleteItem` 操作。您現在可以執行變動以在資料表中刪除項目。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation deletePost {
    deletePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 文章會從 DynamoDB 刪除。請注意， AWS AppSync 會傳回從 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
      }
    }
  }
  ```

如果對 `deletePost` 的呼叫實際上是從 DynamoDB 遭到刪除的呼叫，則只會傳回值。
+ 再次選擇 **Execute query (執行查詢)**。
+ 此呼叫仍會成功，但不會傳回任何值。

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

現在讓我們嘗試刪除文章，但這次是指定 `expectedValue`。首先，您需要建立新文章，因為您刪除的正好是您到目前為止所用的文章。
+ 將以下變動貼到 **Queries (查詢)** 窗格：

  ```
  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
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 新建立文章的結果應該會出現在查詢窗格右側的結果窗格中。請記下新建立的物件的 `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
      }
    }
  }
  ```

現在讓我們嘗試刪除該文章，但我們將錯誤的值放入 `expectedVersion`：
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation deletePost {
    deletePost(
      id:123
      expectedVersion: 9999
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。

  ```
  {
    "data": {
      "deletePost": null
    },
    "errors": [
      {
        "path": [
          "deletePost"
        ],
        "data": {
          "id": "123",
          "author": "AUTHORNAME",
          "title": "Our second post!",
          "content": "A new post.",
          "url": "https://aws.amazon.com/appsync/",
          "ups": 1,
          "downs": 0,
          "version": 1
        },
        "errorType": "DynamoDB:ConditionalCheckFailedException",
        "locations": [
          {
            "line": 2,
            "column": 3
          }
        ],
        "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)"
      }
    ]
  }
  ```

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

  ```
  mutation deletePost {
    deletePost(
      id:123
      expectedVersion: 1
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 這次請求成功，並傳回從 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
      }
    }
  }
  ```
+ 再次選擇 **Execute query (執行查詢)**。
+ 呼叫仍然成功，但這次不會傳回任何值，因為文章已在 DynamoDB 中刪除。

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

## 設定 allPost 解析程式 (DynamoDB Scan)
<a name="setting-up-the-allpost-resolver-dynamodb-scan"></a>

到目前為止，如果您知道要尋找之每個文章的 `id`，API 才有用。讓我們新增新的解析程式，傳回資料表中的所有文章。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中的 `Query` 類型以新增 `allPost` 查詢，如下所示：

  ```
  type Query {
      allPost(count: Int, nextToken: String): PaginatedPosts!
      getPost(id: ID): Post
  }
  ```
+ 新增 `PaginationPosts` 類型：

  ```
  type PaginatedPosts {
      posts: [Post!]!
      nextToken: String
  }
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**查詢**類型上新建立的 **allPost** 欄位，然後選擇**連接**。
+ 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "Scan"
      #if( ${context.arguments.count} )
          ,"limit": $util.toJson($context.arguments.count)
      #end
      #if( ${context.arguments.nextToken} )
          ,"nextToken": $util.toJson($context.arguments.nextToken)
      #end
  }
  ```

  此解析程式有兩個選用引數：`count` 會指定單一呼叫中傳回的項目上限數，`nextToken` 可用來擷取下一組結果 (稍後您將顯示 `nextToken` 值來自何處)。
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```

   **注意：**到目前為止，此回應映射範本與所有其他範本皆不同。`allPost` 查詢結果是 `PaginatedPosts`，其中包含文章清單和分頁字符。此物件的形狀與從 the AWS AppSync DynamoDB Resolver 傳回的物件不同：文章清單在 AWS AppSync DynamoDB Resolver 結果`items`中稱為 ，但在 `posts`中稱為 `PaginatedPosts`。
+ 選擇**儲存**。

如需有關 `Scan` 要求映射的詳細資訊，請參閱[掃描](aws-appsync-resolver-mapping-template-reference-dynamodb-scan.md)參考文件。

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

現在已設定解析程式， AWS AppSync 知道如何將傳入`allPost`查詢轉譯為 DynamoDB`Scan` 操作。您現在可以掃描資料表來擷取所有文章。

在您可以嘗試之前，您必須將一些資料填入資料表，因為您已刪除目前為止所使用的項目。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格：

  ```
  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 }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。

現在，讓我們來掃描資料表，一次會傳回五個結果。
+ 將以下查詢貼到 **Queries (查詢)** 窗格中：

  ```
  query allPost {
    allPost(count: 5) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 前五篇文章應該會出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

  ```
  {
    "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": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRkJEdXdUK09hcnovRGhNTGxLTGdMUEFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF6ajFodkhKU1paT1pncTRaUUNBUkNBZ2dHWnJiR1dQWGxkMDB1N0xEdGY4Z2JsbktzRjRua1VCcks3TFJLcjZBTFRMeGFwVGJZMDRqOTdKVFQyYVRwSzdzbVdtNlhWWFVCTnFIOThZTzBWZHVkdDI2RlkxMHRqMDJ2QTlyNWJTUWpTbWh6NE5UclhUMG9KZWJSQ2JJbXBlaDRSVlg0Tis0WTVCN1IwNmJQWWQzOVhsbTlUTjBkZkFYMVErVCthaXZoNE5jMk50RitxVmU3SlJ5WmpzMEFkSGduM3FWd2VrOW5oeFVVd3JlK1loUks5QkRzemdiMDlmZmFPVXpzaFZ4cVJRbC93RURlOTcrRmVJdXZNby9NZ1F6dUdNbFRyalpNR3FuYzZBRnhwa0VlZTFtR0FwVDFISElUZlluakptYklmMGUzUmcxbVlnVHVSbDh4S0trNmR0QVoraEhLVDhuNUI3VnF4bHRtSnlNUXBrZGl6KzkyL3VzNDl4OWhrMnVxSW01ZFFwMjRLNnF0dm9ZK1BpdERuQTc5djhzb0grVytYT3VuQ2NVVDY4TVZ1Wk5KYkRuSEFSSEVlaTlVNVBTelU5RGZ6d2pPdmhqWDNJMWhwdWUrWi83MDVHVjlPQUxSTGlwZWZPeTFOZFhwZTdHRDZnQW00bUJUK2c1eC9Ec3ZDbWVnSDFDVXRTdHVuU1ZFa2JpZytQRC9oMUwyRTNqSHhVQldaa28yU256WUc0cG0vV1RSWkFVZHZuQT09In0="
      }
    }
  }
  ```

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

  ```
  query allPost {
    allPost(
      count: 5
      nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRlluNktJRWl6V0ZlR3hJOVJkaStrZUFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5cW8yUGFSZThnalFpemRCTUNBUkNBZ2dHWk1JODhUNzhIOFVUZGtpdFM2ZFluSWRyVDg4c2lkN1RjZzB2d1k3VGJTTWpSQ2U3WjY3TkUvU2I1dWNETUdDMmdmMHErSGJSL0pteGRzYzVEYnE1K3BmWEtBdU5jSENJdWNIUkJ0UHBPWVdWdCtsS2U5L1pNcWdocXhrem1RaXI1YnIvQkt6dU5hZmJCdE93NmtoM2Jna1BKM0RjWWhpMFBGbmhMVGg4TUVGSjBCcXg3RTlHR1V5N0tUS0JLZlV3RjFQZ0JRREdrNzFYQnFMK2R1S2IrVGtZZzVYMjFrc3NyQmFVTmNXZmhTeXE0ZUJHSWhqZWQ5c3VKWjBSSTc2ZnVQdlZkR3FLNENjQmxHYXhpekZnK2pKK1FneEU1SXduRTNYYU5TR0I4QUpmamR2bU1wbUk1SEdvWjlMUUswclczbG14RDRtMlBsaTNLaEVlcm9pem5zcmdINFpvcXIrN2ltRDN3QkJNd3BLbGQzNjV5Nnc4ZnMrK2FnbTFVOUlKOFFrOGd2bEgySHFROHZrZXBrMWlLdWRIQ25LaS9USnBlMk9JeEVPazVnRFlzRTRUU09HUlVJTkxYY2MvdW1WVEpBMUthV2hWTlAvdjNlSnlZQUszbWV6N2h5WHVXZ1BkTVBNWERQdTdjVnVRa3EwK3NhbGZOd2wvSUx4bHNyNDVwTEhuVFpyRWZvVlV1bXZ5S2VKY1RUU1lET05hM1NwWEd2UT09In0="
    ) {
      posts {
        id
        author
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 剩餘的四篇文章應該會出現在查詢窗格右側的結果窗格中。在這組結果中沒有 `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 解析程式 (DynamoDB 查詢）
<a name="setting-up-the-allpostsbyauthor-resolver-ddb-query"></a>

除了掃描 DynamoDB 的所有文章之外，您也可以查詢 DynamoDB 以擷取特定作者建立的文章。您先前建立的 DynamoDB 資料表已有`GlobalSecondaryIndex`稱為 的 `author-index` ，您可以使用 DynamoDB`Query` 操作來擷取特定作者建立的所有文章。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中的 `Query` 類型以新增 `allPostsByAuthor` 查詢，如下所示：

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

   **注意：**這會使用與您在 `allPost` 查詢中使用的相同 `PaginatedPosts` 類型。
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**查詢**類型上新建立的 **allPostsByAuthor** 欄位，然後選擇**連接**。
+ 在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "Query",
      "index" : "author-index",
      "query" : {
        "expression": "author = :author",
          "expressionValues" : {
            ":author" : $util.dynamodb.toDynamoDBJson($context.arguments.author)
          }
      }
      #if( ${context.arguments.count} )
          ,"limit": $util.toJson($context.arguments.count)
      #end
      #if( ${context.arguments.nextToken} )
          ,"nextToken": "${context.arguments.nextToken}"
      #end
  }
  ```

  如同 `allPost` 解析程式，此解析程式有兩個可選引數：`count`，其會指定項目的上限數，以在單一呼叫中傳回，以及 `nextToken`，其可用來擷取接下來的結果組 (您可從先前的呼叫中取得 `nextToken` 值)。
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```

   **注意**：這是與您在 `allPost` 解析程式中所使用的相同回應映射範本。
+ 選擇**儲存**。

如需有關 `Query` 要求映射的詳細資訊，請參閱[查詢](aws-appsync-resolver-mapping-template-reference-dynamodb-query.md)參考文件。

### 呼叫 API 來查詢某個作者的所有文章
<a name="call-the-api-to-query-all-posts-by-an-author"></a>

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

在這麼做之前，讓我們再將一些文章填入資料表，因為目前為止每篇文章的作者都一樣。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格：

  ```
  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 }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。

現在，讓我們查詢資料表，傳回 `Nadia` 撰寫的所有文章。
+ 將以下查詢貼到 **Queries (查詢)** 窗格中：

  ```
  query allPostsByAuthor {
    allPostsByAuthor(author: "Nadia") {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ `Nadia` 撰寫的所有文章應該會出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

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

分頁適用於 `Query`，如同它適用於 `Scan`。例如，讓我們查詢 `AUTHORNAME` 的所有文章，一次取得五篇。
+ 將以下查詢貼到 **Queries (查詢)** 窗格中：

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "AUTHORNAME"
      count: 5
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ `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": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ=="
      }
    }
  }
  ```
+ 使用之前查詢傳回的值更新 `nextToken` 引數，如下所示：

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "AUTHORNAME"
      count: 5
      nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ=="
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ `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` 類型為統一金鑰/值物件。您也可以使用 AWS AppSyncDynamoDB 解析程式建立複雜物件的模型，例如集合、清單和映射。

讓我們將 `Post` 類型更新為包含標籤。文章可以有 0 個或多個標籤，這些標籤會以字串集的形式存放在 DynamoDB 中。您也會設定一些變動來新增和移除標籤，以及新查詢以掃描含特定標籤的文章。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中的 `Post` 類型以新增 `tags` 欄位，如下所示：

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
  }
  ```
+ 修改 **Schema (結構描述)** 窗格中的 `Query` 類型以新增 `allPostsByTag` 查詢，如下所示：

  ```
  type Query {
    allPostsByTag(tag: String!, count: Int, nextToken: String): PaginatedPosts!
    allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts!
    allPost(count: Int, nextToken: String): PaginatedPosts!
    getPost(id: ID): Post
  }
  ```
+ 修改 **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!
  }
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**查詢**類型上新建立的 **allPostsByTag** 欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "Scan",
      "filter": {
        "expression": "contains (tags, :tag)",
          "expressionValues": {
            ":tag": $util.dynamodb.toDynamoDBJson($context.arguments.tag)
          }
      }
      #if( ${context.arguments.count} )
          ,"limit": $util.toJson($context.arguments.count)
      #end
      #if( ${context.arguments.nextToken} )
          ,"nextToken": $util.toJson($context.arguments.nextToken)
      #end
  }
  ```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立的 **addTag** 欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "ADD tags :tags, version :plusOne",
          "expressionValues" : {
              ":tags" : { "SS": [ $util.toJson($context.arguments.tag) ] },
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立的 **removeTag** 欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "DELETE tags :tags ADD version :plusOne",
          "expressionValues" : {
              ":tags" : { "SS": [ $util.toJson($context.arguments.tag) ] },
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```
+ 選擇**儲存**。

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

現在您已設定解析程式， AWS AppSync 知道如何將傳入的 `removeTag`、 `addTag`和 `allPostsByTag`請求轉換為 DynamoDB`UpdateItem` 和 `Scan`操作。

為了嘗試看看，讓我們選擇您先前建立的其中一篇文章。例如，讓我們使用 `Nadia` 撰寫的一篇文章。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下查詢貼到 **Queries (查詢)** 窗格中：

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "Nadia"
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ Nadia 的所有文章應該會出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

  ```
  {
    "data": {
      "allPostsByAuthor": {
        "posts": [
          {
            "id": "10",
            "title": "The cutest dog in the world"
          },
          {
            "id": "11",
            "title": "Did you known...?"
          }
        ],
        "nextToken": null
      }
    }
  }
  ```
+ 我們使用標題為 `"The cutest dog in the world"` 的文章。請記下其 `id`，因為您稍後將會用到它。

現在，讓我們嘗試新增 `dog` 標籤。
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation addTag {
    addTag(id:10 tag: "dog") {
      id
      title
      tags
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 使用新標籤更新的文章。

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

您可以新增更多的標籤，如下所示：
+ 更新變動以將 `tag` 引數變更為 `puppy`。

  ```
  mutation addTag {
    addTag(id:10 tag: "puppy") {
      id
      title
      tags
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 使用新標籤更新的文章。

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

您也可以刪除標籤：
+ 將以下變動貼到 **Queries (查詢)** 窗格。您也必須將 `id` 引數更新為您先前記下的值。

  ```
  mutation removeTag {
    removeTag(id:10 tag: "puppy") {
      id
      title
      tags
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 貼文將會更新，且 `puppy` 標籤將會刪除。

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

您也可以搜尋所有包含標籤的貼文：
+ 將以下查詢貼到 **Queries (查詢)** 窗格中：

  ```
  query allPostsByTag {
    allPostsByTag(tag: "dog") {
      posts {
        id
        title
        tags
      }
      nextToken
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ 具有 `dog` 標籤的所有文章將會傳回，如下所示：

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

## 使用清單和映射
<a name="using-lists-and-maps"></a>

除了使用 DynamoDB 集之外，您還可以使用 DynamoDB 清單和映射在單一物件中建立複雜資料的模型。

讓我們新增功能以將評論新增到文章。這將建模為 DynamoDB 中物件上的映射`Post`物件清單。

 **注意：**在實際應用程式中，您會在它們自己的資料表中塑造評論。在本教學課程中，您將只是將它們新增至 `Post` 資料表。
+ 選擇 **Schema (結構描述)** 標籤。
+ 修改 **Schema (結構描述)** 窗格中新增 `Comment` 類型，如下所示：

  ```
  type Comment {
      author: String!
      comment: String!
  }
  ```
+ 修改 **Schema (結構描述)** 窗格中的 `Post` 類型以新增 `comments` 欄位，如下所示：

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
    comments: [Comment!]
  }
  ```
+ 修改 **Schema (結構描述)** 窗格中的 `Mutation` 類型來新增 `addComment` 變動，如下所示：

  ```
  type Mutation {
    addComment(id: ID!, author: String!, comment: String!): Post
    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!
  }
  ```
+ 選擇**儲存**。
+ 在右側的**資料類型**窗格中，尋找**變動**類型上新建立的 **addComment** 欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **PostDynamoDBTable**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

  ```
  {
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
      "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
    },
    "update" : {
      "expression" : "SET comments = list_append(if_not_exists(comments, :emptyList), :newComment) ADD version :plusOne",
      "expressionValues" : {
        ":emptyList": { "L" : [] },
        ":newComment" : { "L" : [
          { "M": {
            "author": $util.dynamodb.toDynamoDBJson($context.arguments.author),
            "comment": $util.dynamodb.toDynamoDBJson($context.arguments.comment)
            }
          }
        ] },
        ":plusOne" : $util.dynamodb.toDynamoDBJson(1)
      }
    }
  }
  ```

  這個更新表達式將會將包含新評論的清單附加到現有的 `comments` 清單。如果清單不存在，則會建立一個。
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

  ```
  $utils.toJson($context.result)
  ```
+ 選擇**儲存**。

### 呼叫 API 以新增評論
<a name="call-the-api-to-add-a-comment"></a>

現在您已設定解析程式， AWS AppSync 知道如何將傳入`addComment`的請求轉換為 DynamoDB`UpdateItem` 操作。

讓我們透過將評論新增至您已新增標籤的相同文章，以嘗試此操作。
+ 選擇 **Queries (查詢)** 標籤。
+ 將以下查詢貼到 **Queries (查詢)** 窗格中：

  ```
  mutation addComment {
    addComment(
      id:10
      author: "Steve"
      comment: "Such a cute dog."
    ) {
      id
      comments {
        author
        comment
      }
    }
  }
  ```
+ 選擇 **Execute query (執行查詢)** (橘色播放按鈕)。
+ Nadia 的所有文章應該會出現在查詢窗格右側的結果窗格中。其看起來與下列類似：

  ```
  {
    "data": {
      "addComment": {
        "id": "10",
        "comments": [
          {
            "author": "Steve",
            "comment": "Such a cute dog."
          }
        ]
      }
    }
  }
  ```

如果您多次執行要求，系統會將多個評論附加到清單。

## 結論
<a name="conclusion"></a>

在本教學課程中，您已建置 API，讓我們可以使用 AWS AppSync 和 GraphQL 在 DynamoDB 中操作 Post 物件。如需詳細資訊，請參閱[解析程式映射範本參考](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference)。

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

若要刪除您為此教學課程建立的 DynamoDB 資料表和 IAM 角色，您可以執行下列動作來刪除`AWSAppSyncTutorialForAmazonDynamoDB`堆疊，或造訪 CloudFormation 主控台並刪除堆疊：

```
aws cloudformation delete-stack \
    --stack-name AWSAppSyncTutorialForAmazonDynamoDB
```

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

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

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

下列範例顯示寫入 的 Lambda 函數`Node.js`，該函數會在部落格文章應用程式中對部落格文章執行不同的操作。

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var 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"} };

    var 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.");
    switch(event.field) {
        case "getPost":
            var id = event.arguments.id;
            callback(null, posts[id]);
            break;
        case "allPosts":
            var values = [];
            for(var d in posts){
                values.push(posts[d]);
            }
            callback(null, values);
            break;
        case "addPost":
            // return the arguments back
            callback(null, event.arguments);
            break;
        case "addPostErrorWithData":
            var id = event.arguments.id;
            var result = posts[id];
            // attached additional error information to the post
            result.errorMessage = 'Error with the mutation, data has changed';
            result.errorType = 'MUTATION_ERROR';
            callback(null, result);
            break;
        case "relatedPosts":
            var id = event.source.id;
            callback(null, relatedPosts[id]);
            break;
        default:
            callback("Unknown field, unable to resolve" + event.field, null);
            break;
    }
};
```

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

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

使用 AWS 管理主控台或 AWS CloudFormation 堆疊建立此 Lambda 函數。若要從 CloudFormation 堆疊建立函數，您可以使用下列 AWS Command Line Interface (AWS CLI) 命令：

```
aws cloudformation create-stack --stack-name AppSyncLambdaExample \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml \
--capabilities CAPABILITY_NAMED_IAM
```

您也可以從這裡在美國西部 （奧勒岡） AWS 區域中啟動 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/lambda/LambdaCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml)

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

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

選擇**建立資料來源**，輸入易記**的資料來源名稱** （例如 **Lambda**)，然後針對**資料來源類型**選擇 **AWS Lambda 函數**。針對**區域**，選擇與函數相同的區域。（如果您從提供的 CloudFormation 堆疊建立函數，則函數可能位於 **US-WEST-2**。) 針對**函數 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"></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"></a>

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

若要建立解析程式，您需要映射範本。若要進一步了解映射範本，請參閱 [Resolver Mapping Template Overview](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)。

如需 Lambda 映射範本的詳細資訊，請參閱 [Resolver mapping template reference for Lambda](resolver-mapping-template-reference-lambda.md#aws-appsync-resolver-mapping-template-reference-lambda)。

在此步驟中，您將解析程式連接至下列欄位的 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`。

然後，在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。

之後，選擇您的 Lambda 資料來源。在 **request mapping template (請求映射範本)** 區段中，選擇 **Invoke And Forward Arguments (叫用並轉發引數)**。

修改 `payload` 物件以新增欄位名稱。您的範本看起來應該如下所示：

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "getPost",
        "arguments":  $utils.toJson($context.arguments)
    }
}
```

在 **response mapping template (回應映射範本)** 區段中，選擇 **Return Lambda Result (傳回 Lambda 結果)**。

在此案例中，使用原本的基礎範本。它看起來應該如下所示：

```
$utils.toJson($context.result)
```

選擇**儲存**。您已成功連接第一個解析程式。對剩餘欄位重複此操作，如下所示：

`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` 請求映射範本：

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "addPost",
        "arguments":  $utils.toJson($context.arguments)
    }
}
```

`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` 回應映射範本：

```
$utils.toJson($context.result)
```

`allPosts: [Post]` 請求映射範本：

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "allPosts"
    }
}
```

`allPosts: [Post]` 回應映射範本：

```
$utils.toJson($context.result)
```

`Post.relatedPosts: [Post]` 請求映射範本：

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "relatedPosts",
        "source":  $utils.toJson($context.source)
    }
}
```

`Post.relatedPosts: [Post]` 回應映射範本：

```
$utils.toJson($context.result)
```

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

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

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

### addPost Mutation
<a name="addpost-mutation"></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"></a>

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

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

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

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

任何指定的欄位解析度都可能導致錯誤。透過 AWS AppSync，您可以從下列來源引發錯誤：
+ 請求或回應映射範本
+ Lambda 函式

### 從映射範本
<a name="from-the-mapping-template"></a>

若要引發刻意錯誤，您可以使用 Velocity 範本語言 (VTL) 範本中的`$utils.error`協助程式方法。它使用 `errorMessage`、`errorType`，及選用的 `data` 值做為引數。發生錯誤時，`data` 可將額外的資料傳回給用戶端。`data` 物件會新增到 GraphQL 最後回應中的 `errors`。

以下範例說明如何在 `Post.relatedPosts: [Post]` 回應映射範本中使用它：

```
$utils.error("Failed to fetch relatedPosts", "LambdaFailure", $context.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"></a>

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

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

下列 Lambda 函式會引發錯誤：

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    callback("I fail. Always.");
};
```

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

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "Lambda:Handled",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "I fail. Always."
        }
    ]
}
```

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

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

```
query allPosts {
    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 {
    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 函數解析單一指定文章的相關文章清單。

為了示範，讓我們將 `Post.relatedPosts: [Post]` 解析程式切換為啟用批次的解析程式。

在 AWS AppSync 主控台的右側，選擇現有的`Post.relatedPosts: [Post]`解析程式。將請求映射範本變更為以下內容：

```
{
    "version": "2017-02-28",
    "operation": "BatchInvoke",
    "payload": {
        "field": "relatedPosts",
        "source":  $utils.toJson($context.source)
    }
}
```

只有 `operation` 欄位從 `Invoke` 變更為 `BatchInvoke`。承載欄位現在會成為範本中指定之任何項目的陣列。在此範例中，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
]
```

Node.js 中的下列 Lambda 函數示範 `Post.relatedPosts` 欄位的此批次處理功能，如下所示：

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var 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"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length);
    // event is now an array
    var field = event[0].field;
    switch(field) {
        case "relatedPosts":
            var results = [];
            // the response MUST contain the same number
            // of entries as the payload array
            for (var i=0; i< event.length; i++) {
                console.log("post {}", JSON.stringify(event[i].source));
                results.push(relatedPosts[event[i].source.id]);
            }
            console.log("results {}", JSON.stringify(results));
            callback(null, results);
            break;
        default:
            callback("Unknown field, unable to resolve" + field, null);
            break;
    }
};
```

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

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

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

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

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var 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"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length);
    // event is now an array
    var field = event[0].field;
    switch(field) {
        case "relatedPosts":
            var results = [];
            results.push({ 'data': relatedPosts['1'] });
            results.push({ 'data': relatedPosts['2'] });
            results.push({ 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' });
            results.push(null);
            results.push({ 'data': relatedPosts['3'], 'errorMessage': 'Error Happened with last result', 'errorType': 'ERROR' });
            callback(null, results);
            break;
        default:
            callback("Unknown field, unable to resolve" + field, null);
            break;
    }
};
```

在此範例中，下列回應映射範本會剖析 Lambda 函數的每個項目，並引發任何發生的錯誤：

```
#if( $context.result && $context.result.errorMessage )
    $utils.error($context.result.errorMessage, $context.result.errorType, $context.result.data)
#else
    $utils.toJson($context.result.data)
#end
```

此範例會傳回類似下列的 GraphQL 回應：

```
{
  "data": {
    "allPosts": [
      {
        "id": "1",
        "relatedPostsPartialErrors": [
          {
            "id": "4",
            "title": "Fourth book"
          }
        ]
      },
      {
        "id": "2",
        "relatedPostsPartialErrors": [
          {
            "id": "3",
            "title": "Third book"
          },
          {
            "id": "5",
            "title": "Fifth book"
          }
        ]
      },
      {
        "id": "3",
        "relatedPostsPartialErrors": null
      },
      {
        "id": "4",
        "relatedPostsPartialErrors": null
      },
      {
        "id": "5",
        "relatedPostsPartialErrors": null
      }
    ]
  },
  "errors": [
    {
      "path": [
        "allPosts",
        2,
        "relatedPostsPartialErrors"
      ],
      "errorType": "ERROR",
      "locations": [
        {
          "line": 4,
          "column": 9
        }
      ],
      "message": "Error Happened"
    },
    {
      "path": [
        "allPosts",
        4,
        "relatedPostsPartialErrors"
      ],
      "data": [
        {
          "id": "2",
          "title": "Second book"
        },
        {
          "id": "1",
          "title": "First book"
        }
      ],
      "errorType": "ERROR",
      "locations": [
        {
          "line": 4,
          "column": 9
        }
      ],
      "message": "Error Happened with last result"
    }
  ]
}
```

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

根據預設，使用 時`BatchInvoke`， AWS AppSync 會以最多五個項目的批次傳送請求到您的 Lambda 函數。您可以設定 Lambda 解析程式的批次大小上限。

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

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --request-mapping-template "<template>" --response-mapping-template "<template>" --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

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

您也可以使用下列命令來啟用和設定 Direct Lambda 解析程式上的批次處理：

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

### 使用 VTL 範本的批次大小上限組態
<a name="configure-max-batch-size-vtl"></a>

對於具有 VTL 請求中範本的 Lambda 解析程式，除非其已直接指定為 VTL 中的`BatchInvoke`操作，否則最大批次大小不會生效。同樣地，如果您執行最上層變動，則不會針對變動執行批次處理，因為 GraphQL 規格需要依序執行平行變動。

例如，採取下列變動：

```
type Mutation {
    putItem(input: Item): Item
    putItems(inputs: [Item]): [Item]
}
```

使用第一個變動，我們可以建立 10`Items`，如以下程式碼片段所示：

```
mutation MyMutation {
    v1: putItem($someItem1) {
        id,
        name
    }
    v2: putItem($someItem2) {
        id,
        name
    }
    v3: putItem($someItem3) {
        id,
        name
    } 
    v4: putItem($someItem4) {
        id,
        name
    }
    v5: putItem($someItem5) {
        id,
        name
    }
    v6: putItem($someItem6) {
        id,
        name
    } 
    v7: putItem($someItem7) {
        id,
        name
    }
    v8: putItem($someItem8) {
        id,
        name
    }
    v9: putItem($someItem9) {
        id,
        name
    }
    v10: putItem($someItem10) {
        id,
        name
    }
}
```

在此範例中，即使 Lambda Resolver 中的批次大小上限設為 10，也不會在 10 的`Items`群組中批次處理 。相反地，它們會根據 GraphQL 規格循序執行。

若要執行實際批次變動，您可以使用第二個變動遵循下列範例：

```
mutation MyMutation {
    putItems([$someItem1, $someItem2, $someItem3,$someItem4, $someItem5, $someItem6, 
    $someItem7, $someItem8, $someItem9, $someItem10]) {
    id,
    name
    }
}
```

如需搭配 Direct Lambda 解析程式使用批次處理的詳細資訊，請參閱 [Direct Lambda 解析程式](resolver-mapping-template-reference-lambda.md#direct-lambda-resolvers)。

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

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

如需詳細資訊，請參閱 [ OpenSearch 的解析程式映射範本參考](resolver-mapping-template-reference-elasticsearch.md#aws-appsync-resolver-mapping-template-reference-elasticsearch)。

## 一鍵設定
<a name="one-click-setup"></a>

若要在已設定 Amazon OpenSearch Service 的 AWS AppSync 中自動設定 GraphQL 端點，您可以使用此 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/appsynces.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/appsynces.yml)

 AWS CloudFormation 部署完成後，您可以直接跳至[執行 GraphQL 查詢和變動](#tutorial-elasticsearch-resolvers-perform-queries-mutations)。

## 建立新的 OpenSearch Service 網域
<a name="create-a-new-es-domain"></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 帳戶中的美國西部 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"></a>

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

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "Stmt1234234",
            "Effect": "Allow",
            "Action": [
                "es:ESHttpDelete",
                "es:ESHttpHead",
                "es:ESHttpGet",
                "es:ESHttpPost",
                "es:ESHttpPut"
            ],
            "Resource": [
                "arn:aws:es:us-east-1:111122223333:domain/democluster/*"
            ]
        }
    ]
}
```

------

您還需要為該角色設定與 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 網域的適當動作和資源。請注意，**主體**將是 AppSync 資料來源角色，如果您讓主控台建立此角色，可以在 IAM 主控台中找到。

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111122223333:role/service-role/APPSYNC_DATASOURCE_ROLE"
            },
            "Action": [
                "es:ESHttpDelete",
                "es:ESHttpHead",
                "es:ESHttpGet",
                "es:ESHttpPost",
                "es:ESHttpPut"
            ],
            "Resource": "arn:aws:es:us-east-1:111122223333:domain/DOMAIN_NAME/*"
        }
    ]
}
```

------

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

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

```
 schema {
   query: Query
   mutation: Mutation
 }

 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` 中，選擇 **Attach resolver (附加解析程式)**。在**動作功能表中**，選擇**更新執行時間**，然後選擇**單位解析程式 （僅限 VTL)**。然後，選擇您的 OpenSearch Service 資料來源。在 **request mapping template (請求映射範本)** 區段中，選取 **Query posts (查詢文章)** 的下拉式清單，來取得基本範本。將 `path` 修改為 `/post/_search`。它看起來應該如下所示：

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path":"/post/_search",
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "from":0,
            "size":50
        }
    }
}
```

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

在**回應映射範本**區段下，如果您想要從 OpenSearch Service 查詢取回資料結果並轉譯為 GraphQL，則需要指定適當的`_source`篩選條件。使用下列的範本：

```
[
    #foreach($entry in $context.result.hits.hits)
    #if( $velocityCount > 1 ) , #end
    $utils.toJson($entry.get("_source"))
    #end
]
```

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

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

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

現在選擇**連接解析程式**，然後選取 OpenSearch Service 資料來源，但在**回應映射範本**中使用下列範例：

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path":"/post/_search",
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "from":0,
            "size":50,
            "query":{
                "match" :{
                    "author": $util.toJson($context.arguments.author)
                }
            }
        }
    }
}
```

請注意，`body` 會填入 `author` 欄位的查詢詞語，此欄位會做為引數，從用戶端直接傳來。您可以選擇性地預先填入資訊 (例如標準的文字內容)，或甚至使用其他[公用程式](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference)。

如果您正在使用此解析程式，請使用與先前範例相同的資訊，來填寫**回應映射範本**。

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

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

返回 AWS AppSync 主控台中的**結構描述**頁面，並為`addPost()`變動選取**連接解析程式**。再次選取 OpenSearch Service `Posts` 資料來源，並針對結構描述使用以下**回應映射範本**：

```
{
    "version":"2017-02-28",
    "operation":"PUT",
    "path": $util.toJson("/post/_doc/$context.arguments.id"),
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "id": $util.toJson($context.arguments.id),
            "author": $util.toJson($context.arguments.author),
            "ups": $util.toJson($context.arguments.ups),
            "downs": $util.toJson($context.arguments.downs),
            "url": $util.toJson($context.arguments.url),
            "content": $util.toJson($context.arguments.content),
            "title": $util.toJson($context.arguments.title)
        }
    }
}
```

和之前相同，這是如何建構資料結構的一個範例。如果有不同的欄位名稱或索引，您需要適當地更新 `path` 與 `body`。此範例也示範了如何利用 `$context.arguments`，以 GraphQL 變動引數來填寫範本。

在繼續之前，請使用下列回應映射範本，這會傳回變動操作的結果或錯誤資訊做為輸出：

```
#if($context.error)
    $util.toJson($ctx.error)
#else
    $util.toJson($context.result)
#end
```

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

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

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path": $util.toJson("post/_doc/$context.arguments.id"),
    "params":{
        "headers":{},
        "queryString":{},
        "body":{}
    }
}
```

由於上述的 `path` 使用 `id` 引數與空的內容，因此這會傳回空的單一文件。不過，因為現在傳回的是單一項目而非清單，您需要使用下列回應映射範本：

```
$utils.toJson($context.result.get("_source"))
```

## 執行查詢與變動
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations"></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 searchPosts {
    searchPosts {
        id
        title
        author
        content
    }
}
```

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

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

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

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

本機解析程式適用於多種使用案例。最常見的使用案例是發佈通知而不觸發資料來源呼叫。為了示範此使用案例，我們來建置一個分頁應用程式，讓使用者可以互相傳遞頁面。此範例使用*訂閱*，因此，如果您不熟悉*訂閱*，可以遵循[即時資料](aws-appsync-real-time-data.md)教學課程。

## 建立分頁應用程式
<a name="create-the-paging-application"></a>

在我們的分頁應用程式中，用戶端可以訂閱收件匣，並向其他用戶端傳送頁面。每個頁面皆包含訊息。結構描述如下：

```
schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}

type Subscription {
    inbox(to: String!): Page
    @aws_subscribe(mutations: ["page"])
}

type Mutation {
    page(body: String!, to: String!): Page!
}

type Page {
    from: String
    to: String!
    body: String!
    sentAt: String!
}

type Query {
    me: String
}
```

現在將解析程式附加到 `Mutation.page` 欄位。在 **Schema (結構描述)** 窗格中，按一下右側窗格上欄位定義旁邊的 *Attach Resolver (附加解析程式)*。建立類型為*無*的新資料來源，將其命名為 *PageDataSource*。

在請求映射範本部分，輸入：

```
{
  "version": "2017-02-28",
  "payload": {
    "body": $util.toJson($context.arguments.body),
    "from": $util.toJson($context.identity.username),
    "to":  $util.toJson($context.arguments.to),
    "sentAt": "$util.time.nowISO8601()"
  }
}
```

在回應映射範本部分，則選取預設的 *Forward the result (轉發結果)*。儲存您的解析程式。您的應用程式現已就緒，可以開始傳遞頁面！

## 傳送和訂閱頁面
<a name="send-and-subscribe-to-pages"></a>

用戶端必須先訂閱收件匣，才能接收頁面。

現在就讓我們在 **Queries (查詢)** 窗格中執行 `inbox` 訂閱：

```
subscription Inbox {
    inbox(to: "Nadia") {
        body
        to
        from
        sentAt
    }
}
```

 每當叫用`Mutation.page`變動時，*Nadia* 都會收到頁面。現在透過執行變動來叫用變動：

```
mutation Page {
    page(to: "Nadia", body: "Hello, World!") {
        body
        to
        from
        sentAt
    }
}
```

我們剛示範如何使用本機解析程式，方法是傳送頁面並接收頁面，而不使用 leaving AWS AppSync。

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

GraphQL 結構描述中的解析程式與欄位具有一對一的關係，以及高度的彈性。由於資料來源是在解析程式上與結構描述個別獨立設定，因此您可以透過不同的資料來源來解析或操縱 GraphQL 的類型，對結構描述進行混合和匹配，最大程度地滿足您的需求。

下列範例案例示範如何在結構描述中混合和比對資料來源。開始之前，我們建議您熟悉設定 Amazon DynamoDB 和 Amazon OpenSearch Service AWS Lambda的資料來源和解析程式，如先前教學所述。

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

下列結構描述的類型為 `Post`，已定義 3 個`Query`操作和 3 個`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
}
```

在此範例中，總共要連結 6 個解析程式。其中一種可能的方法是讓所有這些都來自稱為 的 Amazon DynamoDB 資料表`Posts`，其中 `AllPosts`會執行掃描並`searchPosts`執行查詢，如 [DynamoDB 解析程式映射範本參考](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb)中所述。不過，還有其他方法可以滿足您的業務需求，例如從 Lambda 或 OpenSearch Service 解析這些 GraphQL 查詢。

## 透過解析程式來修改資料
<a name="alter-data-through-resolvers"></a>

您可能需要將資料庫的結果傳回給用戶端，例如 DynamoDB （或 Amazon Aurora)，其中某些屬性已變更。這可能是資料類型的格式所造成 (例如用戶端上時間戳記的差異)，或是為了處理回溯相容性的問題。基於說明目的，在下列範例中， AWS Lambda 函數會在每次叫用 GraphQL 解析程式時指派隨機數字，藉此操控部落格文章的向上和向下投票：

```
'use strict';
const doc = require('dynamodb-doc');
const dynamo = new doc.DynamoDB();

exports.handler = (event, context, callback) => {
    const payload = {
        TableName: 'Posts',
        Limit: 50,
        Select: 'ALL_ATTRIBUTES',
    };

    dynamo.scan(payload, (err, data) => {
        const result = { data: data.Items.map(item =>{
            item.ups = parseInt(Math.random() * (50 - 10) + 10, 10);
            item.downs = parseInt(Math.random() * (20 - 0) + 0, 10);
            return item;
        }) };
        callback(err, result.data);
    });
};
```

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

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

對於某些應用程式，您可以對 DynamoDB 執行變動或簡單查詢，並具有背景程序傳輸文件到 OpenSearch Service。然後，您只需將 `searchPosts` Resolver 連接至 OpenSearch Service 資料來源，並使用 GraphQL 查詢傳回搜尋結果 （來自源自 DynamoDB 的資料）。在為您的應用程式加入進階的搜尋操作時 (例如關鍵字詞、模糊字詞比對或甚至地理空間查詢)，這可以是非常強大的功能。從 DynamoDB 傳輸資料可以透過 ETL 程序完成，或者您也可以使用 Lambda 從 DynamoDB 串流。您可以使用您 AWS 帳戶中美國西部 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/multipledatasource/appsyncesdbstream.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml) 

此範例中的結構描述可讓您使用 DynamoDB 解析程式新增文章，如下所示：

```
mutation add {
    putPost(author:"Nadia"
        title:"My first post"
        content:"This is some test content"
        url:"https://aws.amazon.com/appsync/"
    ){
        id
        title
    }
}
```

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

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

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

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

 **注意**：這些程式碼僅為示範。

```
var AWS = require('aws-sdk');
var path = require('path');
var stream = require('stream');

var esDomain = {
    endpoint: 'https://opensearch-domain-name.REGION.es.amazonaws.com',
    region: 'REGION',
    index: 'id',
    doctype: 'post'
};

var endpoint = new AWS.Endpoint(esDomain.endpoint)
var creds = new AWS.EnvironmentCredentials('AWS');

function postDocumentToES(doc, context) {
    var req = new AWS.HttpRequest(endpoint);

    req.method = 'POST';
    req.path = '/_bulk';
    req.region = esDomain.region;
    req.body = doc;
    req.headers['presigned-expires'] = false;
    req.headers['Host'] = endpoint.host;

    // Sign the request (Sigv4)
    var signer = new AWS.Signers.V4(req, 'es');
    signer.addAuthorization(creds, new Date());

    // Post document to ES
    var send = new AWS.NodeHttpClient();
    send.handleRequest(req, null, function (httpResp) {
        var body = '';
        httpResp.on('data', function (chunk) {
            body += chunk;
        });
        httpResp.on('end', function (chunk) {
            console.log('Successful', body);
            context.succeed();
        });
    }, function (err) {
        console.log('Error: ' + err);
        context.fail();
    });
}

exports.handler = (event, context, callback) => {
    console.log("event => " + JSON.stringify(event));
    var posts = '';

    for (var i = 0; i < event.Records.length; i++) {
        var eventName = event.Records[i].eventName;
        var actionType = '';
        var image;
        var noDoc = false;
        switch (eventName) {
            case 'INSERT':
                actionType = 'create';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'MODIFY':
                actionType = 'update';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'REMOVE':
            actionType = 'delete';
                image = event.Records[i].dynamodb.OldImage;
                noDoc = true;
                break;
        }

        if (typeof image !== "undefined") {
            var postData = {};
            for (var key in image) {
                if (image.hasOwnProperty(key)) {
                    if (key === 'postId') {
                        postData['id'] = image[key].S;
                    } else {
                        var val = image[key];
                        if (val.hasOwnProperty('S')) {
                            postData[key] = val.S;
                        } else if (val.hasOwnProperty('N')) {
                            postData[key] = val.N;
                        }
                    }
                }
            }

            var action = {};
            action[actionType] = {};
            action[actionType]._index = 'id';
            action[actionType]._type = 'post';
            action[actionType]._id = postData['id'];
            posts += [
                JSON.stringify(action),
            ].concat(noDoc?[]:[JSON.stringify(postData)]).join('\n') + '\n';
        }
    }
    console.log('posts:',posts);
    postDocumentToES(posts, context);
};
```

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

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

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

搭配 DynamoDB in AWS AppSync 使用批次操作是一項進階技術，需要多加思考和了解您的後端操作和資料表結構。此外，批次操作 in AWS AppSync 與非批次操作有兩個主要差異：
+ 對於解析程式將會存取的所有資料表，資料來源角色都必須具有權限。
+ 解析程式資料表規格包含於映射範本中。

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

與其他解析程式一樣，您需要建立資料來源 in AWS AppSync，並建立角色或使用現有的角色。由於批次操作在 DynamoDB 資料表上需要不同的許可，因此您需要為讀取或寫入動作授予設定的角色許可：

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem"
            ],
            "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"></a>

為簡單起見，我們將針對本教學課程中使用的所有解析程式，使用相同的資料來源。在**資料來源**索引標籤上，建立新的 DynamoDB 資料來源，並將其命名為 **BatchTutorial**。資料表可以使用任何名稱，因為資料表名稱在批次作業中是指定為請求映射範本的一部分。我們會將資料表命名為 `empty`。

在本教學課程中，具備下列內嵌政策的任何角色皆可使用：

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

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

例如，假設有一個名為 **Posts** 的資料表，您想要利用批次操作來新增和移除此資料表的項目。使用下列的結構描述，請注意，我們會將 ID 清單傳入查詢：

```
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]
}

schema {
    query: Query
    mutation: Mutation
}
```

使用下列**請求映射範本**將解析程式連接至 `batchAdd()` 欄位。這會自動擷取 GraphQL `input PostInput` 類型的每個項目，並建置 `BatchPutItem` 作業所需的對應圖：

```
#set($postsdata = [])
#foreach($item in ${ctx.args.posts})
    $util.qr($postsdata.add($util.dynamodb.toMapValues($item)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        "Posts": $utils.toJson($postsdata)
    }
}
```

在此情況中的**回應映射範本**只是簡單的直接傳遞，但是該資料表名稱會以 `..data.Posts` 的形式附加至內容物件，如下所示：

```
$util.toJson($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** 資料表。

接著，使用下列**請求映射範本**將解析程式連接至 `batchGet()` 欄位。這會自動擷取 GraphQL `ids:[]` 類型的每個項目，並建置 `BatchGetItem` 作業所需的對應圖：

```
#set($ids = [])
#foreach($id in ${ctx.args.ids})
    #set($map = {})
    $util.qr($map.put("id", $util.dynamodb.toString($id)))
    $util.qr($ids.add($map))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "Posts": {
            "keys": $util.toJson($ids),
            "consistentRead": true,
            "projection" : {
                "expression" : "#id, title",
                "expressionNames" : { "#id" : "id"}
                }
        }
    }
}
```

**回應映射範本**仍然只是簡單的直接傳遞，該資料表名稱也會再次地以 `..data.Posts` 的形式附加至內容物件：

```
$util.toJson($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` 值。

最後，使用以下**請求映射範本**將解析程式連接到 `batchDelete()` 欄位。這會自動擷取 GraphQL `ids:[]` 類型的每個項目，並建置 `BatchGetItem` 作業所需的對應圖：

```
#set($ids = [])
#foreach($id in ${ctx.args.ids})
    #set($map = {})
    $util.qr($map.put("id", $util.dynamodb.toString($id)))
    $util.qr($ids.add($map))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchDeleteItem",
    "tables" : {
        "Posts": $util.toJson($ids)
    }
}
```

**回應映射範本**仍然只是簡單的直接傳遞，該資料表名稱也會再次地以 `..data.Posts` 的形式附加至內容物件：

```
$util.toJson($ctx.result.data.Posts)
```

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

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

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

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

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

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

作為先決條件，讓我們先建立兩個 DynamoDB 資料表；**locationReadings** 將存放感應器位置讀數，而 **temperatureReadings** 將存放感應器溫度讀數。這兩個資料表碰巧擁有相同的主索引鍵結構：`sensorId (String)` 為分割區索引鍵、`timestamp (String)` 為排序索引鍵。

讓我們使用下列 GraphQL 結構描述：

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

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

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

選取 `Mutation.recordReadings` 欄位旁的**連接**。在下一個畫面中，選取在教學課程開始時所建立的同一個 `BatchTutorial` 資料來源。

讓我們來新增下列請求映射範本：

 **請求映射範本** 

```
## Convert tempReadings arguments to DynamoDB objects
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
    $util.qr($tempReadings.add($util.dynamodb.toMapValues($reading)))
#end

## Convert locReadings arguments to DynamoDB objects
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
    $util.qr($locReadings.add($util.dynamodb.toMapValues($reading)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        "locationReadings": $utils.toJson($locReadings),
        "temperatureReadings": $utils.toJson($tempReadings)
    }
}
```

如您所見，`BatchPutItem` 操作可讓我們指定多個資料表。

讓我們使用下列回應映射範本。

 **回應映射範本** 

```
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
    ## Append a GraphQL error for that field in the GraphQL response
    $utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also returns data for the field in the GraphQL response
$utils.toJson($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
    }
  }
}
```

我們會在一次的變動中傳送 10 個感應器讀數，讀數的資料會分置於兩個資料表。使用 DynamoDB 主控台來驗證資料是否同時顯示在 **locationReadings** 和 **temperatureReadings** 資料表中。

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

同樣地，我們也需要刪除分批的感應器讀數。讓我們使用 `Mutation.deleteReadings` GraphQL 欄位來進行這項動作。選取 `Mutation.recordReadings` 欄位旁的**連接**。在下一個畫面中，選取在教學課程開始時所建立的同一個 `BatchTutorial` 資料來源。

讓我們使用下列請求映射範本。

 **請求映射範本** 

```
## Convert tempReadings arguments to DynamoDB primary keys
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
    #set($pkey = {})
    $util.qr($pkey.put("sensorId", $reading.sensorId))
    $util.qr($pkey.put("timestamp", $reading.timestamp))
    $util.qr($tempReadings.add($util.dynamodb.toMapValues($pkey)))
#end

## Convert locReadings arguments to DynamoDB primary keys
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
    #set($pkey = {})
    $util.qr($pkey.put("sensorId", $reading.sensorId))
    $util.qr($pkey.put("timestamp", $reading.timestamp))
    $util.qr($locReadings.add($util.dynamodb.toMapValues($pkey)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchDeleteItem",
    "tables" : {
        "locationReadings": $utils.toJson($locReadings),
        "temperatureReadings": $utils.toJson($tempReadings)
    }
}
```

這個回應映射範本和我們用於 `Mutation.recordReadings` 的相同。

 **回應映射範本** 

```
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
    ## Append a GraphQL error for that field in the GraphQL response
    $utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also return data for the field in the GraphQL response
$utils.toJson($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
    }
  }
}
```

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

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

我們寵物健康狀態 (Pet Health) 應用程式另一個常用的操作，就是在特定的時間點擷取感應器的讀數。試試將解析程式連接到結構描述中的 `Query.getReadings` GraphQL 欄位。選取 **Attach (附加)**，然後在下一個畫面上，選取在教學課程開始時所建立的同一個 `BatchTutorial` 資料來源。

讓我們來新增下列請求映射範本。

 **請求映射範本** 

```
## Build a single DynamoDB primary key,
## as both locationReadings and tempReadings tables
## share the same primary key structure
#set($pkey = {})
$util.qr($pkey.put("sensorId", $ctx.args.sensorId))
$util.qr($pkey.put("timestamp", $ctx.args.timestamp))

{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "locationReadings": {
            "keys": [$util.dynamodb.toMapValuesJson($pkey)],
            "consistentRead": true
        },
        "temperatureReadings": {
            "keys": [$util.dynamodb.toMapValuesJson($pkey)],
            "consistentRead": true
        }
    }
}
```

請注意，我們現在使用的是 **BatchGetItem** 操作。

我們的回應映射範本將會有些微的不同，因為我們選擇傳回 `SensorReading` 清單。讓我們將叫用結果映射到所需的結構。

 **回應映射範本** 

```
## Merge locationReadings and temperatureReadings
## into a single list
## __typename needed as schema uses an interface
#set($sensorReadings = [])

#foreach($locReading in $ctx.result.data.locationReadings)
    $util.qr($locReading.put("__typename", "LocationReading"))
    $util.qr($sensorReadings.add($locReading))
#end

#foreach($tempReading in $ctx.result.data.temperatureReadings)
    $util.qr($tempReading.put("__typename", "TemperatureReading"))
    $util.qr($sensorReadings.add($tempReading))
#end

$util.toJson($sensorReadings)
```

儲存解析程式並導覽至 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"></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"></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"></a>

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

我們所編寫的回應映射範本，只熟悉和著重於結果的資料。

回應映射範本：

```
$util.toJson($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-template-execution"></a>

如果從用戶端的角度，部分故障應視為完全故障，則您可以中止範本的執行，來防止傳回資料。`$util.error(...)` 公用程式方法可確實完成這項動作。

回應映射範本：

```
## there was an error let's mark the entire field
## as failed and do not return any data back in the response
#if ($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($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"></a>

在某些情況中，為提供更好的使用者體驗，應用程式可以傳回部分結果，並通知其用戶端有未處理的項目。用戶端可以決定是否要重試，或是轉譯錯誤後傳回給最終使用者。`$util.appendError(...)` 是一項公用程式方法，可讓應用程式的設計人員將錯誤附加於文字內容，但不會干擾到範本的評估，進而完成前述的動作。評估範本後， AWS AppSync 會將任何內容錯誤附加至 GraphQL 回應的錯誤區塊，以處理這些錯誤。

回應映射範本：

```
#if ($ctx.error)
    ## pass the unprocessed keys back to the caller via the `errorInfo` field
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($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. (...)"
    }
  ]
}
```

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

AWS AppSync 支援在單一區域中的一或多個資料表中使用 Amazon DynamoDB 交易操作。支援的操作包括 `TransactGetItems` 和 `TransactWriteItems`。透過使用這些 in AWS AppSync 功能，您可以執行下列任務：
+ 在單次查詢中傳遞索引鍵清單，並從資料表傳回結果
+ 在單次查詢中讀取一個或多個資料表的記錄
+ 以「全有或全無」的方式，將交易中的記錄寫入一個或多個資料表
+ 符合某些條件時執行交易

## 許可
<a name="permissions"></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"></a>

為簡單起見，我們將針對本教學課程中使用的所有解析程式，使用相同的資料來源。在**資料來源**索引標籤上，建立新的 DynamoDB 資料來源，並將其命名為 **TransactTutorial**。資料表可以使用任何名稱，因為資料表名稱在交易操作中是指定為請求映射範本的一部分。我們會將資料表命名為 `empty`。

我們將兩個資料表稱為 **savingAccounts** 和 **checkingAccounts**，兩者皆具有 `accountNumber` 做為分割區索引鍵，以及 **transactionHistory** 資料表，具有 `transactionId` 做為分割區索引鍵。

在本教學課程中，具備下列內嵌政策的任何角色皆可使用。將 `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"></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
}

schema {
    query: Query
    mutation: Mutation
}
```

### TransactWriteItems - 填入帳戶
<a name="transactwriteitems-populate-accounts"></a>

為了在帳戶之間轉帳，我們需要在表格中填入詳細資料。我們將使用 GraphQL 操作 `Mutation.populateAccounts` 來執行此作業。

在結構描述區段中，按一下`Mutation.populateAccounts`操作旁的**連接**。前往 VTL 單位解析程式，然後選擇相同的`TransactTutorial`資料來源。

現在使用下列請求映射範本：

 **請求映射範本** 

```
#set($savingAccountTransactPutItems = [])
#set($index = 0)
#foreach($savingAccount in ${ctx.args.savingAccounts})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($savingAccount.accountNumber)))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("username", $util.dynamodb.toString($savingAccount.username)))
    $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($savingAccount.balance)))
    #set($index = $index + 1)
    #set($savingAccountTransactPutItem = {"table": "savingAccounts",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($savingAccountTransactPutItems.add($savingAccountTransactPutItem))
#end

#set($checkingAccountTransactPutItems = [])
#set($index = 0)
#foreach($checkingAccount in ${ctx.args.checkingAccounts})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($checkingAccount.accountNumber)))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("username", $util.dynamodb.toString($checkingAccount.username)))
    $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($checkingAccount.balance)))
    #set($index = $index + 1)
    #set($checkingAccountTransactPutItem = {"table": "checkingAccounts",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($checkingAccountTransactPutItems.add($checkingAccountTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountTransactPutItems))
$util.qr($transactItems.addAll($checkingAccountTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```

以及下列回應映射範本：

 **回應映射範本** 

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..5])
    $util.qr($checkingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))

$util.toJson($transactionResult)
```

儲存解析程式並導覽至 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
    }
  }
}
```

我們在一個變動中填入 3 個存款帳戶和 3 個支票帳戶。

使用 DynamoDB 主控台來驗證資料是否同時顯示在 **savingAccounts** 和 **checkingAccounts** 資料表中。

### TransactWriteItems - 轉帳
<a name="transactwriteitems-transfer-money"></a>

使用下列**請求映射範本**將解析程式連接至`transferMoney`變動。請注意，`amounts`、`savingAccountNumbers` 以及 `checkingAccountNumbers` 的值是相同的。

```
#set($amounts = [])
#foreach($transaction in ${ctx.args.transactions})
    #set($attributeValueMap = {})
    $util.qr($attributeValueMap.put(":amount", $util.dynamodb.toNumber($transaction.amount)))
    $util.qr($amounts.add($attributeValueMap))
#end

#set($savingAccountTransactUpdateItems = [])
#set($index = 0)
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.savingAccountNumber)))
    #set($update = {})
    $util.qr($update.put("expression", "SET balance = balance - :amount"))
    $util.qr($update.put("expressionValues", $amounts[$index]))
    #set($index = $index + 1)
    #set($savingAccountTransactUpdateItem = {"table": "savingAccounts",
        "operation": "UpdateItem",
        "key": $keyMap,
        "update": $update})
    $util.qr($savingAccountTransactUpdateItems.add($savingAccountTransactUpdateItem))
#end

#set($checkingAccountTransactUpdateItems = [])
#set($index = 0)
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.checkingAccountNumber)))
    #set($update = {})
    $util.qr($update.put("expression", "SET balance = balance + :amount"))
    $util.qr($update.put("expressionValues", $amounts[$index]))
    #set($index = $index + 1)
    #set($checkingAccountTransactUpdateItem = {"table": "checkingAccounts",
        "operation": "UpdateItem",
        "key": $keyMap,
        "update": $update})
    $util.qr($checkingAccountTransactUpdateItems.add($checkingAccountTransactUpdateItem))
#end

#set($transactionHistoryTransactPutItems = [])
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("transactionId", $util.dynamodb.toString(${utils.autoId()})))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("from", $util.dynamodb.toString($transaction.savingAccountNumber)))
    $util.qr($attributeValues.put("to", $util.dynamodb.toString($transaction.checkingAccountNumber)))
    $util.qr($attributeValues.put("amount", $util.dynamodb.toNumber($transaction.amount)))
    #set($transactionHistoryTransactPutItem = {"table": "transactionHistory",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($transactionHistoryTransactPutItems.add($transactionHistoryTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountTransactUpdateItems))
$util.qr($transactItems.addAll($checkingAccountTransactUpdateItems))
$util.qr($transactItems.addAll($transactionHistoryTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```

我們將會在單一 `TransactWriteItems` 操作中有 3 筆銀行交易。使用下列**回應映射範本**：

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..5])
    $util.qr($checkingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($transactionHistory = [])
#foreach($index in [6..8])
    $util.qr($transactionHistory.add(${ctx.result.keys[$index]}))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))
$util.qr($transactionResult.put('transactionHistory', $transactionHistory))

$util.toJson($transactionResult)
```

現在導覽至 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
    }
  }
}
```

我們已傳送單一變動中的 2 筆銀行交易。使用 DynamoDB 主控台來驗證資料是否顯示在 **savingAccounts**、**checkingAccounts** 和 **transactionHistory** 資料表中。

### TransactGetItems - 擷取帳戶
<a name="transactgetitems-retrieve-accounts"></a>

為了擷取單一交易請求中存款帳戶和支票帳戶的詳細資料，我們會將解析程式附加至結構模式中的 `Query.getAccounts` GraphQL 操作。選取**連接**，前往 VTL 單元解析程式，然後在下一個畫面選擇在教學課程開始時建立的相同`TransactTutorial`資料來源。設定範本，如下所示：

 **請求映射範本** 

```
#set($savingAccountsTransactGets = [])
#foreach($savingAccountNumber in ${ctx.args.savingAccountNumbers})
    #set($savingAccountKey = {})
    $util.qr($savingAccountKey.put("accountNumber", $util.dynamodb.toString($savingAccountNumber)))
    #set($savingAccountTransactGet = {"table": "savingAccounts", "key": $savingAccountKey})
    $util.qr($savingAccountsTransactGets.add($savingAccountTransactGet))
#end

#set($checkingAccountsTransactGets = [])
#foreach($checkingAccountNumber in ${ctx.args.checkingAccountNumbers})
    #set($checkingAccountKey = {})
    $util.qr($checkingAccountKey.put("accountNumber", $util.dynamodb.toString($checkingAccountNumber)))
    #set($checkingAccountTransactGet = {"table": "checkingAccounts", "key": $checkingAccountKey})
    $util.qr($checkingAccountsTransactGets.add($checkingAccountTransactGet))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountsTransactGets))
$util.qr($transactItems.addAll($checkingAccountsTransactGets))

{
    "version" : "2018-05-29",
    "operation" : "TransactGetItems",
    "transactItems" : $util.toJson($transactItems)
}
```

 **回應映射範本** 

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.items[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..4])
    $util.qr($checkingAccounts.add($ctx.result.items[$index]))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))

$util.toJson($transactionResult)
```

儲存解析程式並導覽至 AWS AppSync 主控台的**查詢**區段。若要擷取存款帳戶和支票帳戶，請執行下列查詢：

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

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

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

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

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

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

## 一鍵設定
<a name="one-click-setup"></a>

如果您想要在設定 HTTP 端點的情況下自動設定 GraphQL 端點 in AWS AppSync （使用 Amazon API Gateway 和 Lambda)，您可以使用下列 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/http/http-full.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-full.yaml)

## 建立 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：
+ 開啟 AWS AppSync 主控台，然後選擇**建立 API**。
+ 針對 API 名稱，輸入 `UserData`。
+ 選擇 **Custom schema (自訂結構描述)**。
+ 選擇**建立**。

 AWS AppSync 主控台會使用 API 金鑰身分驗證模式為您建立新的 GraphQL API。您可以使用主控台來設定 GraphQL API 的其餘部分，並在本教學的其餘部分中對其執行查詢。

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

現在您有一個 GraphQL API，讓我們來建立 GraphQL 結構描述吧。從 AWS AppSync 主控台中的結構描述編輯器，請確定您的結構描述符合下列結構描述：

```
schema {
    query: Query
    mutation: Mutation
}

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 資料來源，請執行以下作業：
+ 在 **DataSources** 標籤中，選擇 **New (新增)**，然後輸入易記的資料來源名稱 (例如，`HTTP`)。
+ 在 **Data source type (資料來源類型)** 中選擇 **HTTP**。
+ 將端點設定為建立的 API Gateway 端點。請確定您沒有在端點中包含階段名稱。

 **注意：**目前 AWS AppSync 僅支援公有端點。

 **注意：**如需認證授權機構的詳細資訊， AWS AppSync 請參閱 [AWS AppSync HTTPS 端點的認證授權機構 (CA)](http-cert-authorities.md#aws-appsync-http-certificate-authorities)。

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

在此步驟中，您會將 http 資料來源連線到 **getUser** 查詢。

設定解析程式：
+ 選擇 **Schema (結構描述)** 標籤。
+ 在**查詢**類型下右側的**資料類型**窗格中，尋找 **getUser** 欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **HTTP**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

```
{
    "version": "2018-05-29",
    "method": "GET",
    "params": {
        "headers": {
            "Content-Type": "application/json"
        }
    },
    "resourcePath": $util.toJson("/v1/users/${ctx.args.id}")
}
```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

```
## return the body
#if($ctx.result.statusCode == 200)
    ##if response is 200
    $ctx.result.body
#else
    ##if response is not 200, append the response to error block.
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```
+ 選擇 **Query (查詢)** 標籤，然後執行以下查詢：

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

這應該會傳回以下回應：

```
{
    "data": {
        "getUser": {
            "id": "1",
            "username": "nadia"
        }
    }
}
```
+ 選擇 **Schema (結構描述)** 標籤。
+ 在 **Mutation** 下右側的**資料類型**窗格中，尋找 **addUser** 欄位，然後選擇**連接**。
+ 在 **Data source name (資料來源名稱)** 中，選擇 **HTTP**。
+ 將以下內容貼到 **Configure the request mapping template (設定請求映射範本)** 區段：

```
{
    "version": "2018-05-29",
    "method": "POST",
    "resourcePath": "/v1/users",
    "params":{
      "headers":{
        "Content-Type": "application/json",
      },
      "body": $util.toJson($ctx.args.userInput)
    }
}
```
+ 將以下內容貼到 **Configure the response mapping template (設定回應映射範本)** 區段：

```
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
## if the response status code is not 200, then return an error. Else return the body **
#if($ctx.result.statusCode == 200)
    ## If response is 200, return the body.
    $ctx.result.body
#else
    ## If response is not 200, append the response to error block.
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```
+ 選擇 **Query (查詢)** 標籤，然後執行以下查詢：

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

這應該會傳回以下回應：

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

## 叫用 AWS 服務
<a name="invoking-aws-services"></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 Serverless v2 AWS AppSync
<a name="tutorial-rds-resolvers"></a>

使用 將您的 GraphQL API 連接至 Aurora Serverless 資料庫 AWS AppSync。此整合可讓您透過 GraphQL 查詢、變動和訂閱執行 SQL 陳述式，讓您有彈性地與關聯式資料互動。

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

**優勢**
+ GraphQL 與關聯式資料庫之間的無縫整合
+ 能夠透過 GraphQL 介面執行 SQL 操作
+ 搭配 Aurora Serverless v2 的無伺服器可擴展性
+ 透過 AWS Secrets Manager 安全存取資料
+ 透過輸入淨化防止 SQL 注入
+ 靈活的查詢功能，包括篩選和範圍操作

**常用案例**
+ 使用關聯式資料需求建置可擴展的應用程式
+ 建立同時需要 GraphQL 彈性和 SQL 資料庫功能的 APIs 
+ 透過 GraphQL 變動和查詢管理資料操作
+ 實作安全資料庫存取模式

在本教學課程中，您將學到以下內容。
+ 設定 Aurora Serverless v2 叢集
+ 啟用資料 API 功能
+ 建立和設定資料庫結構
+ 定義資料庫操作的 GraphQL 結構描述
+ 實作查詢和變動的解析程式
+ 透過適當的輸入清理來保護您的資料存取
+ 透過 GraphQL 介面執行各種資料庫操作

**Topics**
+ [設定資料庫叢集](#create-cluster)
+ [啟用 Data API](#enable-data-api)
+ [建立資料庫及資料表](#create-database-and-table)
+ [GraphQL 結構描述](#graphql-schema)
+ [將您的 API 連線至資料庫操作](#configuring-resolvers)
+ [透過 API 修改您的資料](#run-mutations)
+ [擷取您的資料](#run-queries)
+ [保護您的資料存取](#input-sanitization)

## 設定資料庫叢集
<a name="create-cluster"></a>

將 Amazon RDS 資料來源新增至 之前 AWS AppSync，您必須先在 Aurora Serverless v2 叢集上啟用資料 API，並使用 **設定秘密***AWS Secrets Manager*。您可以使用 建立 Aurora Serverless v2 叢集 AWS CLI：

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

這會傳回叢集的 ARN。

建立叢集之後，您必須使用下列命令新增 Aurora 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-mysql
```

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

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

您可以使用 AWS Secrets Manager 主控台或 AWS CLI 來建立*秘密*，並使用上`COMPLEX_PASSWORD`一個步驟中的 `USERNAME`和 搭配輸入檔案，如下所示。

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

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

```
aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1
```

這會傳回秘密的 ARN。

 **記下 ARN** (屬於 Aurora Serverless 叢集及 Secret) 以供日後建立資料來源時用於 AppSync 主控台。

## 啟用 Data API
<a name="enable-data-api"></a>

您可以[依照 RDS 文件中的說明](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html)，在您的叢集上啟用 Data API。Data API 必須先行啟用，才能新增為 AppSync 資料來源。

## 建立資料庫及資料表
<a name="create-database-and-table"></a>

啟用資料 API 後，您就可以確保它可與 中的 `aws rds-data execute-statement`命令搭配使用 AWS CLI。這可確保您的 Aurora Serverless 叢集在經過正確設定後，才新增到您的 AppSync API。首先使用 `--sql` 參數建立名為 *TESTDB* 的資料庫，如下所示：

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1"  \
--region us-east-1 --sql "create DATABASE TESTDB"
```

如果這次執行沒有錯誤，則搭配 *create table* 命令新增資料表：

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
 --schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \
 --region us-east-1 \
 --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"
```

如果所有執行都沒有問題，您就可以繼續將該叢集新增到您的 AppSync API。

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

現在您的 Aurora Serverless Data API 已經啟動並搭配資料表運作，我們會建立 GraphQL 結構描述，並連接可執行變動和訂閱的解析程式。在 AWS AppSync 主控台中建立新的 API 並導覽至**結構描述**頁面，然後輸入以下內容：

```
type Mutation {
    createPet(input: CreatePetInput!): Pet
    updatePet(input: UpdatePetInput!): Pet
    deletePet(input: DeletePetInput!): Pet
}

input CreatePetInput {
    type: PetType
    price: Float!
}

input UpdatePetInput {
id: ID!
    type: PetType
    price: Float!
}

input DeletePetInput {
    id: ID!
}

type Pet {
    id: ID!
    type: PetType
    price: Float
}

enum PetType {
    dog
    cat
    fish
    bird
    gecko
}

type Query {
    getPet(id: ID!): Pet
    listPets: [Pet]
    listPetsByPriceRange(min: Float, max: Float): [Pet]
}

schema {
    query: Query
    mutation: Mutation
}
```

 **儲存**您的結構描述並瀏覽到 **Data Sources (資料來源)** 頁面，並建立新的資料來源。選取資料來源類型為** Relational database (關聯式資料庫)**，並提供易記名稱。使用您在上一個步驟中所建立的資料庫名稱，以及您所建立叢集的**叢集 ARN**。處理**角色**時，您可以使用 AppSync 來建立新角色，或是搭配類似下面政策來建立一個角色：

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

****  

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

------

請注意，在您授予角色存取權的這個政策中有 2 個**陳述式** 。第一個**資源**是您的 Aurora Serverless 叢集，第二個資源是您的 AWS Secrets Manager ARN。您將需要先在 AppSync 資料來源組態中**同時**提供兩種 ARN 後，再按一下 **Create (建立)**。

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

```
aws secretsmanager create-secret \
  --name HttpRDSSecret \
  --secret-string file://creds.json \
  --region us-east-1
```

這會傳回秘密的 ARN。在 AWS AppSync 主控台中建立資料來源時，請記下 Aurora Serverless 叢集的 ARN 和秘密以供稍後使用。

### 建置您的資料庫結構
<a name="create-database-and-table"></a>

啟用資料 API 後，您就可以確保它可與 中的 `aws rds-data execute-statement`命令搭配使用 AWS CLI。這將確保您的 Aurora Serverless v2 叢集在新增至 AWS AppSync API 之前設定正確。首先，使用 `--sql` 參數建立名為 *TESTDB* 的資料庫，如下所示。

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

如果執行時沒有錯誤，請使用下列*建立資料表命令新增資料表*。

```
aws rds-data execute-statement \
      --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:http-endpoint-test" \
      --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:testHttp2-AmNvc1" \
      --region us-east-1 \
      --sql "create table Pets(id varchar(200), type varchar(200), price float)" \
      --database "TESTDB"
```

### 設計您的 API 介面
<a name="graphql-schema"></a>

Aurora Serverless v2 Data API 啟動並使用資料表執行後，請建立 GraphQL 結構描述並連接解析程式，以執行變動和訂閱。在 AWS AppSync 主控台中建立新的 API，並導覽至主控台中的**結構描述**頁面，然後輸入以下內容。

```
type Mutation {
        createPet(input: CreatePetInput!): Pet
        updatePet(input: UpdatePetInput!): Pet
        deletePet(input: DeletePetInput!): Pet
    }
    
    input CreatePetInput {
        type: PetType
        price: Float!
    }
    
    input UpdatePetInput {
        id: ID!
        type: PetType
        price: Float!
    }
    
    input DeletePetInput {
        id: ID!
    }
    
    type Pet {
        id: ID!
        type: PetType
        price: Float
    }
    
    enum PetType {
        dog
        cat
        fish
        bird
        gecko
    }
    
    type Query {
        getPet(id: ID!): Pet
        listPets: [Pet]
        listPetsByPriceRange(min: Float, max: Float): [Pet]
    }
    
    schema {
        query: Query
        mutation: Mutation
    }
```

 **儲存**您的結構描述並瀏覽到 **Data Sources (資料來源)** 頁面，並建立新的資料來源。選擇******資料來源類型的關聯式資料庫**，並提供易記的名稱。使用您在上一個步驟中所建立的資料庫名稱，以及您所建立叢集的**叢集 ARN**。對於**角色**，您可以 AWS AppSync 建立新的角色，或使用類似下列的政策來建立角色。

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

****  

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

------

請注意，在您授予角色存取權的這個政策中有 2 個**陳述式** 。第一個**資源**是您的 Aurora Serverless v2 叢集，第二個資源是您的 AWS Secrets Manager ARN。在按一下**建立**之前，您將需要在 AWS AppSync 資料來源組態中提供**這兩個** ARNs。

## 將您的 API 連線至資料庫操作
<a name="configuring-resolvers"></a>

現在我們有有效的 GraphQL 結構描述和 RDS 資料來源，您可以將解析程式連接到結構描述的 GraphQL 欄位。我們的 API 提供下列功能：

1. 使用 *Mutation.createPet* 欄位建立寵物

1. 使用 *Mutation.updatePet* 欄位更新寵物

1. 使用 *Mutation.deletePet* 欄位刪除寵物

1. 透過 *Query.getPet* 欄位取得單一使用

1. 使用 *Query.listPets* 欄位列出所有

1. 使用 *Query.listPetsByPriceRange* 欄位列出價格範圍內的寵物

### Mutation.createPet
<a name="mutation-createpet"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`createPet(input: CreatePetInput!): Pet`。選擇 RDS 資料來源。在 **request mapping template (要求映射範本)** 區段中，新增下列範本：

```
#set($id=$utils.autoId())
{
"version": "2018-05-29",
    "statements": [
        "insert into Pets VALUES (:ID, :TYPE, :PRICE)",
        "select * from Pets WHERE id = :ID"
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

系統會根據陳述式陣列中的順序依序執行 SQL **陳述**式。結果將以相同順序傳回。由於這是一個變動，因此您將在*插入*後執行*選取*陳述式，以擷取遞交的值，以填入 GraphQL 回應映射範本。

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

由於*陳述式*有兩個 SQL 查詢，所以我們需要使用下列程式碼，指定自資料庫傳回矩陣中的第二個結果：`$utils.rds.toJsonString($ctx.result))[1][0])`。

### Mutation.updatePet
<a name="mutation-updatepet"></a>

從 AWS AppSync 主控台的結構描述編輯器中，選擇**連接解析程式**。 `updatePet(input: UpdatePetInput!): Pet`選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("update Pets set type=:TYPE, price=:PRICE WHERE id=:ID"),
        $util.toJson("select * from Pets WHERE id = :ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

在**回應映射範本**區段中，新增下列範本。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

### Mutation.deletePet
<a name="mutation-deletepet"></a>

從 AWS AppSync 主控台的結構描述編輯器中，選擇**連接解析程式**。 `deletePet(input: DeletePetInput!): Pet`選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("select * from Pets WHERE id=:ID"),
        $util.toJson("delete from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id"
    }
}
```

在**回應映射範本**區段中，新增下列範本。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.getPet
<a name="query-getpet"></a>

現在已為您的結構描述建立變動，請連接三個查詢，展示如何取得個別項目、清單和套用 SQL 篩選。從 AWS AppSync 主控台的**結構描述編輯器**中，選擇**連接解析程式**。 `getPet(id: ID!): Pet`選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
"version": "2018-05-29",
        "statements": [
            $util.toJson("select * from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.id"
    }
}
```

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.listPets
<a name="query-listpets"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`getPet(id: ID!): Pet`。選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
    "version": "2018-05-29",
    "statements": [
        "select * from Pets"
    ]
}
```

在**回應映射範本**區段中，新增下列範本。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

### Query.listPetsByPriceRange
<a name="query-listpetsbypricerange"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`getPet(id: ID!): Pet`。選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.max),
        ":MIN": $util.toJson($ctx.args.min)
    }
}
```

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

## 透過 API 修改您的資料
<a name="run-mutations"></a>

現在，您已運用 SQL 陳述式設定您的所有解析程式，並已將 GraphQL API 連接到您的 Serverless Aurora Data API，您可以開始執行變動和查詢了。In AWS AppSync 主控台，選擇**查詢**索引標籤，然後輸入下列內容來建立寵物：

```
mutation add {
    createPet(input : { type:fish, price:10.0 }){
        id
        type
        price
    }
}
```

回應應該包含如下的 *id*、*type* 和 *price*：

```
{
  "data": {
    "createPet": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "type": "fish",
      "price": "10.0"
    }
  }
}
```

您可以執行 *updatePet* 變動，修改此項目：

```
mutation update {
    updatePet(input : {
        id: ID_PLACEHOLDER,
        type:bird,
        price:50.0
    }){
        id
        type
        price
    }
}
```

請注意，我們使用先前從 *createPet* 操作傳回的 *ID*。這將是您的記錄在解析程式運用 `$util.autoId()` 時的唯一值。您可以用類似的方式刪除記錄：

```
mutation delete {
    deletePet(input : {id:ID_PLACEHOLDER}){
        id
        type
        price
    }
}
```

運用第一個變動，搭配 *price* 的幾個不同值，建立一些記錄，然後執行一些查詢。

## 擷取您的資料
<a name="run-queries"></a>

仍在 主控台的**查詢**索引標籤中，請使用下列陳述式列出您已建立的所有記錄。

```
query allpets {
    listPets {
        id
        type
        price
    }
}
```

透過下列 GraphQL 查詢，利用 *Query.listPetsByPriceRange* 映射範本`where price > :MIN and price < :MAX`中具有 的 SQL *WHERE* 述詞：

```
query petsByPriceRange {
    listPetsByPriceRange(min:1, max:11) {
        id
        type
        price
    }
}
```

您應該只會看到 *price* 超過 \$11 元或不到 \$110 元的記錄。最後，您可以執行查詢來擷取個別記錄，如下所示：

```
query onePet {
    getPet(id:ID_PLACEHOLDER){
        id
        type
        price
    }
}
```

## 保護您的資料存取
<a name="input-sanitization"></a>

SQL Injection 是資料庫應用程式中的安全漏洞。當攻擊者透過使用者輸入欄位插入惡意 SQL 程式碼時，就會發生這種情況。這可以允許未經授權存取資料庫資料。建議您在處理所有使用者輸入之前，使用 仔細驗證和清理，`variableMap`以防止 SQL Injection 攻擊。如果未使用變數映射，您必須負責清理其 GraphQL 操作的引數。達成這個淨化的一種方法是先在要求映射範本中提供輸入特定驗證步驟，接著再對 Data API 執行 SQL 陳述式。讓我們來看看如何修改 `listPetsByPriceRange` 範例的請求映射範本。您可以執行以下操作，而不再只是依賴使用者輸入：

```
#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice))

#set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice))


#if (!$validMaxPrice || !$validMinPrice)
    $util.error("Provided price input is not valid.")
#end
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.maxPrice),
        ":MIN": $util.toJson($ctx.args.minPrice)
    }
}
```

在對 Data API 執行解析程式時防堵惡意輸入的另外一種方法，就是同時使用預備的陳述式來搭配預存程序和參數化的輸入。例如，在 `listPets` 的解析程式中，定義下列執行 *select* 為預先準備陳述式的程序：

```
CREATE PROCEDURE listPets (IN type_param VARCHAR(200))
  BEGIN
     PREPARE stmt FROM 'SELECT * FROM Pets where type=?';
     SET @type = type_param;
     EXECUTE stmt USING @type;
     DEALLOCATE PREPARE stmt;
  END
```

在 Aurora Serverless v2 執行個體中建立此項目。

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx"  \
--region us-east-1  --database "DB_NAME" \
--sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"
```

針對 listPets 產生的解析程式碼變得簡單，因為我們現在只要呼叫預存程序。至少，任何字串輸入都應將單引號[逸出](#escaped)。

```
#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type))
#if (!$validType)
    $util.error("Input for 'type' is not valid.", "ValidationError")
#end

{
    "version": "2018-05-29",
    "statements": [
        "CALL listPets(:type)"
    ]
    "variableMap": {
        ":type": $util.toJson($ctx.args.type.replace("'", "''"))
    }
}
```

### 使用逸出字串
<a name="escaped"></a>

使用單引號在 SQL 陳述式中標記字串常值的開頭和結尾，例如 。 `'some string value'`若要允許在字串中使用具有一或多個單引號字元 (`'`) 的字串值，必須以兩個單引號 (`''`) 取代每個字串值。例如，如果輸入字串是 `Nadia's dog`，則您會將其逸出為如下的 SQL 陳述式：

```
update Pets set type='Nadia''s dog' WHERE id='1'
```

# 使用管道解析程式 in AWS AppSync
<a name="tutorial-pipeline-resolvers"></a>

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

AWS AppSync 提供透過單位解析程式將 GraphQL 欄位連接至單一資料來源的簡單方法。不過，執行單一操作可能還是不夠。管道解析程式的功能是對資料來源依序執行操作。在您的 API 中建立函數，並將其連接到管道解析程式。每個函數執行結果都會輸送到下一個函數，直到沒有要執行的函數。使用管道解析程式，您現在可以直接在 AWS AppSync 中建置更複雜的工作流程。在此教學課程中，您會建置一個簡單的圖片檢視應用程式，而使用者可以透過此應用程式來張貼圖片和檢視朋友所張貼的圖片。

## 一鍵設定
<a name="one-click-setup"></a>

如果您想要自動設定 GraphQL 端點 in AWS AppSync，並設定所有解析程式和必要的 AWS 資源，您可以使用下列 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/pipeline/pipeline-resolvers-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-full.yaml)

這個堆疊會在您的帳戶中建立下列資源：
+ 存取您帳戶中資源的 IAM Role for AWS AppSync 
+ 2 個 DynamoDB 資料表
+ 1 個 Amazon Cognito 使用者工具
+ 2 個 Amazon Cognito 使用者集區群組
+ 3 個 Amazon Cognito 使用者集區使用者
+ 1 AWS AppSync API

在 AWS CloudFormation 堆疊建立程序結束時，您會收到三位建立的 Amazon Cognito 使用者各一封電子郵件。每封電子郵件都包含臨時密碼，您可用來以 Amazon Cognito 使用者身分登入 AWS AppSync 主控台。儲存這些密碼，以供教學課程其餘內容使用。

## 手動設定
<a name="manual-setup"></a>

如果您想要透過 AWS AppSync 主控台手動完成step-by-step程序，請遵循下列設定程序。

### 設定您的非 AWS AppSync 資源
<a name="setting-up-your-non-aws-appsync-resources"></a>

API 會與兩個 DynamoDB 資料表通訊：存放圖片**的圖片**資料表，以及存放使用者之間關係的**好友**資料表。API 已設定為使用 Amazon Cognito 使用者集區做為驗證類型。下列 CloudFormation 堆疊會在 帳戶中設定這些資源。

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

在 AWS CloudFormation 堆疊建立程序結束時，您會收到三位建立的 Amazon Cognito 使用者各一封電子郵件。每封電子郵件都包含臨時密碼，您可用來以 Amazon Cognito 使用者身分登入 AWS AppSync 主控台。儲存這些密碼，以供教學課程其餘內容使用。

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

若要在 AWS AppSync 中建立 GraphQL API：

1. 開啟 AWS AppSync 主控台，然後選擇**從頭開始**。 ****

1. 將 API 名稱設定為 `AppSyncTutorial-PicturesViewer`。

1. 選擇**建立**。

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

### 設定 GraphQL API
<a name="configuring-the-graphql-api"></a>

您需要使用您剛建立的 Amazon Cognito 使用者集區來設定 AWS AppSync API。

1. 選擇 **Settings** (設定) 標籤。

1. 在 **Authorization Type (授權類型)** 區段下，選擇 *Amazon Cognito User Pool (Amazon Cognito 使用者集區)*。

1. 在**使用者集區組態**下，為*AWS 區域*選擇 **US-WEST-2**。

1. 選擇 **AppSyncTutorial-UserPool** 使用者集區。

1. 選擇 **DENY** 做為*預設動作*。

1. 保留 **AppId client regex (AppId 用戶端 regex)** 欄位空白。

1. 選擇**儲存**。

API 現在已設定為使用 Amazon Cognito 使用者集區做為驗證類型。

### 設定 DynamoDB 資料表的資料來源
<a name="configuring-data-sources-for-the-ddb-tables"></a>

建立 DynamoDB 資料表之後，請在主控台中導覽至 your AWS AppSync GraphQL API，然後選擇**資料來源**索引標籤。現在，您將為您剛建立的每個 DynamoDB 資料表建立資料來源 in AWS AppSync。

1. 選擇 **Data source (資料來源)** 標籤。

1. 選擇 **New (新建)** 來建立新資料來源。

1. 輸入 `PicturesDynamoDBTable` 做為資料來源名稱。

1. 選擇 **Amazon DynamoDB table (Amazon DynamoDB 資料表)** 做為資料來源類型。

1. 選擇 **US-WEST-2** 做為區域。

1. 從資料表清單中，選擇 **AppSyncTutorial-PicturesDynamoDB**DynamoDB 資料表。

1. 在**建立或使用現有角色**區段中，選擇**現有角色**。

1. 從 CloudFormation 範本中，選擇剛建立的角色。如果您未變更 *ResourceNamePrefix*，角色的名稱應該就是 **AppSyncTutorial-DynamoDBRole**。

1. 選擇**建立**。

針對**好友**資料表重複相同的程序，如果您在建立 CloudFormation 堆疊時未變更 *ResourceNamePrefix* 參數，則 DynamoDB 資料表的名稱應為 **AppSyncTutorial-Friends**。

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

現在資料來源已連接至 DynamoDB 資料表，讓我們建立 GraphQL 結構描述。從 AWS AppSync 主控台的結構描述編輯器，確保您的結構描述符合下列結構描述：

```
schema {
    query: Query
    mutation: Mutation
}

type Mutation {
    createPicture(input: CreatePictureInput!): Picture!
    @aws_auth(cognito_groups: ["Admins"])
    createFriendship(id: ID!, target: ID!): Boolean
    @aws_auth(cognito_groups: ["Admins"])
}

type Query {
    getPicturesByOwner(id: ID!): [Picture]
    @aws_auth(cognito_groups: ["Admins", "Viewers"])
}

type Picture {
    id: ID!
    owner: ID!
    src: String
}

input CreatePictureInput {
    owner: ID!
    src: String!
}
```

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

有些結構描述欄位已標註 *@aws\$1auth* 指令。由於 API 預設動作設定設為 *DENY*，所以 API 會拒絕不是 *@aws\$1auth* 指令提及群組成員的所有使用者。如需如何保護 API 的詳細資訊，您可以讀取 [Security (安全性)](security-authz.md#aws-appsync-security) 頁面。在這種情況下，只有管理使用者有權存取 *Mutation.createPicture* 和 *Mutation.createFriendship* 欄位，而屬於 *Admins (管理員)* 或 *Viewers (瀏覽者)* 群組之成員的使用者可以存取 *Query.getPicturesByOwner* 欄位。其他所有使用者都無存取權。

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

現在，您已備妥有效的 GraphQL 結構描述和兩個資料來源，您可以將解析程式連接到結構描述上的 GraphQL 欄位。API 提供下列功能：
+ 透過 *Mutation.createPicture* 欄位來建立圖片
+ 透過 *Mutation.createFriendship* 欄位來建立朋友關係
+ 透過 *Query.getPicture* 欄位來擷取圖片

#### Mutation.createPicture
<a name="mutation-createpicture"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`createPicture(input: CreatePictureInput!): Picture!`。選擇 DynamoDB*PicturesDynamoDBTable* PicturesDynamoDBTable資料來源。在 **request mapping template (要求映射範本)** 區段中，新增下列範本：

```
#set($id = $util.autoId())

{
    "version" : "2018-05-29",

    "operation" : "PutItem",

    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($id),
        "owner": $util.dynamodb.toDynamoDBJson($ctx.args.input.owner)
    },

    "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
```

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($ctx.result)
```

建立圖片功能完成。您會使用隨機產生的 UUID 做為圖片 ID，並使用 Cognito 使用者名稱做為擁有者，將圖片儲存到 **Pictures (圖片)** 資料表。

#### Mutation.createFriendship
<a name="mutation-createfriendship"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`createFriendship(id: ID!, target: ID!): Boolean`。選擇 DynamoDB**FriendsDynamoDBTable** FriendsDynamoDBTable資料來源。在 **request mapping template (要求映射範本)** 區段中，新增下列範本：

```
#set($userToFriendFriendship = { "userId" : "$ctx.args.id", "friendId": "$ctx.args.target" })
#set($friendToUserFriendship = { "userId" : "$ctx.args.target", "friendId": "$ctx.args.id" })
#set($friendsItems = [$util.dynamodb.toMapValues($userToFriendFriendship), $util.dynamodb.toMapValues($friendToUserFriendship)])

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        ## Replace 'AppSyncTutorial-' default below with the ResourceNamePrefix you provided in the CloudFormation template
        "AppSyncTutorial-Friends": $util.toJson($friendsItems)
    }
}
```

重要：在 **BatchPutItem** 要求範本中，應該要顯示 DynamoDB 資料表的完整名稱。預設資料表名稱為 *AppSyncTutorial-Friends*。如果您使用的資料表名稱不正確，則當 AppSync 嘗試擔任指定的角色時，將會發生錯誤。

為了讓本教學課程方便進行，請依照朋友關係要求已經核准的前提繼續執行，並將朋友關係項目直接存入 **AppSyncTutorialFriends** 資料表。

很快地，您就能為每個朋友關係儲存兩個項目，做為雙向關係。如需代表many-to-many關係的 Amazon DynamoDB 最佳實務的詳細資訊，請參閱 [DynamoDB 最佳實務](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html)。

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
true
```

注意：請確定您的要求範本包含適當的資料表名稱。預設名稱為 *AppSyncTutorial-Friends*，但如果您變更 CloudFormation **ResourceNamePrefix** 參數，則資料表名稱可能就會不同。

#### Query.getPicturesByOwner
<a name="query-getpicturesbyowner"></a>

現在您已經備妥朋友關係和圖片，您還需要提供讓使用者檢視其朋友圖片的權限。為了滿足這項要求，您必須先檢查申請者是內容擁有者的朋友，最後提供圖片查詢功能。

由於此功能需要兩個資料來源操作，所以您要建立兩個函數。第一種函數 **isFriend** 會檢查申請者和擁有者是否為朋友。第二個函數 **getPicturesByOwner** 會擷取具有擁有者 ID 的要求圖片。讓我們來看看下列對於 *Query.getPicturesByOwner* 欄位執行提議解析程式的執行流程：

1. Before 映射範本：準備內容和欄位輸入引數。

1. isFriend 函數：檢查申請者是否為圖片的擁有者。如果沒有，它會透過對好友資料表執行 DynamoDB GetItem 操作，檢查請求者和擁有者使用者是否為好友。

1. getPicturesByOwner 函數：使用*擁有者索引*全域次要索引上的 DynamoDB 查詢操作，從圖片資料表擷取圖片。

1. After 映射範本：映射圖片結果，讓 DynamoDB 屬性正確映射到預期的 GraphQL 類型欄位。

首先，讓我們來建立下列函數。

##### isFriend 函數
<a name="isfriend-function"></a>

1. 選擇 **Functions (函數)** 索引標籤。

1. 選擇 **Create Function (建立函數)** 來建立函數。

1. 輸入 `FriendsDynamoDBTable` 做為資料來源名稱。

1. 對於函數名稱，輸入 *isFriend*。

1. 在要求映射範本文字區域中，貼上下列範本：

   ```
   #set($ownerId = $ctx.prev.result.owner)
   #set($callerId = $ctx.prev.result.callerId)
   
   ## if the owner is the caller, no need to make the check
   #if($ownerId == $callerId)
       #return($ctx.prev.result)
   #end
   
   {
       "version" : "2018-05-29",
   
       "operation" : "GetItem",
   
       "key" : {
           "userId" : $util.dynamodb.toDynamoDBJson($callerId),
           "friendId" : $util.dynamodb.toDynamoDBJson($ownerId)
       }
   }
   ```

1. 在回應映射範本文字區域中，貼上下列範本：

   ```
   #if($ctx.error)
       $util.error("Unable to retrieve friend mapping message: ${ctx.error.message}", $ctx.error.type)
   #end
   
   ## if the users aren't friends
   #if(!$ctx.result)
       $util.unauthorized()
   #end
   
   $util.toJson($ctx.prev.result)
   ```

1. 選擇 **Create Function (建立函數)**。

結果：您已建立 **isFriend** 函數。

##### getPicturesByOwner 函數
<a name="getpicturesbyowner-function"></a>

1. 選擇 **Functions (函數)** 索引標籤。

1. 選擇 **Create Function (建立函數)** 來建立函數。

1. 輸入 `PicturesDynamoDBTable` 做為資料來源名稱。

1. 對於函數名稱，輸入 `getPicturesByOwner`。

1. 在要求映射範本文字區域中，貼上下列範本：

   ```
   {
       "version" : "2018-05-29",
   
       "operation" : "Query",
   
       "query" : {
           "expression": "#owner = :owner",
           "expressionNames": {
               "#owner" : "owner"
           },
           "expressionValues" : {
               ":owner" : $util.dynamodb.toDynamoDBJson($ctx.prev.result.owner)
           }
       },
   
       "index": "owner-index"
   }
   ```

1. 在回應映射範本文字區域中，貼上下列範本：

   ```
   #if($ctx.error)
       $util.error($ctx.error.message, $ctx.error.type)
   #end
   
   $util.toJson($ctx.result)
   ```

1. 選擇 **Create Function (建立函數)**。

結果：您已建立 **getPicturesByOwner** 函數。現在這些函數已經建立完成，請將管道解析程式連接到 *Query.getPicturesByOwner* 欄位。

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`Query.getPicturesByOwner(id: ID!): [Picture]`。在接下來的頁面，選擇會出現在資料來源下拉式清單中的 **Convert to pipeline resolver (轉換成管道解析程式)** 連結。使用下列程序來使用 Before 映射範本：

```
#set($result = { "owner": $ctx.args.id, "callerId": $ctx.identity.username })
$util.toJson($result)
```

在 **after mapping template (After 映射範本)** 區段中，使用下列範本：

```
#foreach($picture in $ctx.result.items)
    ## prepend "src://" to picture.src property
    #set($picture['src'] = "src://${picture['src']}")
#end
$util.toJson($ctx.result.items)
```

選擇 **Create Resolver (建立解析程式)**。您已成功連接第一個管道解析程式。在相同頁面上，新增您之前建立的兩個函數。在函數區段中，選擇 **Add A Function (新增函數)**，然後選擇或輸入第一個函數的名稱，**isFriend** 依照相同程序來處理 **getPicturesByOwner** 函數，新增第二個函數。確定清單中第一個顯示的是 **isFriend** 函數，後面是 **getPicturesByOwner** 函數。您可以使用上下箭頭，重新安排管道中的函數執行順序。

現在，管道解析程式已經建立完成，而且您已連接函數，讓我們來測試新建立的 GraphQL API。

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

首先，您必須透過您先前建立的管理員使用者，執行少量變動來填入圖片和朋友關係。在 AWS AppSync 主控台的左側，選擇**查詢**索引標籤。

### createPicture Mutation
<a name="createpicture-mutation"></a>

1. In AWS AppSync 主控台，選擇**查詢**索引標籤。

1. 選擇 **Login With User Pools (登入使用者集區)**。

1. 在模態中，輸入隨著 CloudFormation 堆疊建立的 Cognito Sample Client ID，例如，37solo6mmhh7k4v63cqdfgdg5d。

1. 輸入您以參數傳入 CloudFormation 堆疊的使用者名稱。預設為 **nadia**。

1. 使用透過電子郵件寄送，且由您提供做為 CloudFormation 堆疊 (例如，*UserPoolUserEmail*) 參數的臨時密碼。

1. 選擇 **Login (登入)**。您現在應該會看到此按鈕已重新命名為 **Logout nadia**，或是您在建立 CloudFormation 堆疊 (也就是 *UserPoolUsername*) 時所選擇的使用者名稱。

讓我們傳送幾個 *createPicture* 變動來填入該圖片資料表。在主控台中，執行以下 GraphQL 查詢：

```
mutation {
  createPicture(input:{
    owner: "nadia"
    src: "nadia.jpg"
  }) {
    id
    owner
    src
  }
}
```

回應看起來應如下所示：

```
{
  "data": {
    "createPicture": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "owner": "nadia",
      "src": "nadia.jpg"
    }
  }
}
```

讓我們新增更多圖片：

```
mutation {
  createPicture(input:{
    owner: "shaggy"
    src: "shaggy.jpg"
  }) {
    id
    owner
    src
  }
}
```

```
mutation {
  createPicture(input:{
    owner: "rex"
    src: "rex.jpg"
  }) {
    id
    owner
    src
  }
}
```

您已透過管理使用者的身分，使用 **nadia** 新增三張圖片。

### createFriendship 變動
<a name="createfriendship-mutation"></a>

讓我們來新增朋友關係項目。在主控台中，執行下列變動。

注意：您仍必須登入管理使用者 (預設管理使用者是 **nadia**)。

```
mutation {
  createFriendship(id: "nadia", target: "shaggy")
}
```

回應看起來應該會像這樣：

```
{
  "data": {
    "createFriendship": true
  }
}
```

 **nadia** 和 **shaggy** 是朋友。**rex** 不是任何人的朋友。

### getPicturesByOwner 查詢
<a name="getpicturesbyowner-query"></a>

第一步是使用 Cognito 使用者集區，以及本教學課程一開始所設定的登入資料，登入成為 **nadia** 使用者。登入為 **nadia** 時，擷取 **shaggy** 擁有的圖片。

```
query {
    getPicturesByOwner(id: "shaggy") {
        id
        owner
        src
    }
}
```

由於 **nadia** 和 **shaggy** 是朋友，所以查詢應該會傳回相對應的圖片。

```
{
  "data": {
    "getPicturesByOwner": [
      {
        "id": "05a16fba-cc29-41ee-a8d5-4e791f4f1079",
        "owner": "shaggy",
        "src": "src://shaggy.jpg"
      }
    ]
  }
}
```

同樣地，嘗試擷取自己圖片的 **nadia** 也能順利擷取。為了避免在這種情況下執行 **isFriend** GetItem 操作，管道解析程式已完成相關的最佳化處理。嘗試下列查詢：

```
query {
    getPicturesByOwner(id: "nadia") {
        id
        owner
        src
    }
}
```

如果您啟用登入您的 API (在 **Settings** 窗格)，則當偵錯層級設定為 **ALL (全部)**，並再次執行相同查詢時，就會傳回欄位執行的日誌。透過查看日誌，您可以判斷 **isFriend** 函數是否在**請求映射範本**階段提早傳回：

```
{
  "errors": [],
  "mappingTemplateType": "Request Mapping",
  "path": "[getPicturesByOwner]",
  "resolverArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/types/Query/fields/getPicturesByOwner",
  "functionArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/functions/o2f42p2jrfdl3dw7s6xub2csdfs",
  "functionName": "isFriend",
  "earlyReturnedValue": {
    "owner": "nadia",
    "callerId": "nadia"
  },
  "context": {
    "arguments": {
      "id": "nadia"
    },
    "prev": {
      "result": {
        "owner": "nadia",
        "callerId": "nadia"
      }
    },
    "stash": {},
    "outErrors": []
  },
  "fieldInError": false
}
```

*earlyReturnedValue* 索引鍵代表由 *\$1return* 指令傳回的資料。

最後，即使 **rex** 是 **Viewers** Cognito UserPool 群組的成員，但因為 **rex** 不是任何人的朋友，所以他無法存取 **shaggy** 或 **nadia** 所擁有的任何圖片。如果您以 **rex** 登入主控台，並執行下列查詢時：

```
query {
    getPicturesByOwner(id: "nadia") {
        id
        owner
        src
    }
}
```

您會收到下列關於未經授權的錯誤：

```
{
  "data": {
    "getPicturesByOwner": null
  },
  "errors": [
    {
      "path": [
        "getPicturesByOwner"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 9,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access getPicturesByOwner on type Query"
    }
  ]
}
```

這表示您已成功使用管道解析程式，實作複雜的授權程序。

# 在 中的版本控制資料來源上使用 Delta Sync 操作 AWS AppSync
<a name="tutorial-delta-sync"></a>

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

用戶端應用程式 in AWS AppSync 透過將 GraphQL 回應快取到本機行動/網路應用程式中的磁碟來存放資料。已建立版本的資料來源和 `Sync` 操作可讓客戶使用單一解析程式來執行同步處理程序。透過這些查詢，用戶端可以使用從可能包含大量記錄的一個 base 查詢結果補充其本機快取，然後僅接收從上次查詢後有所更動的資料 (*delta 更新*)。藉由允許用戶端透過初始請求和其他請求的遞增性更新來執行快取的基本填入，所以您可將用戶端應用程式的計算工作移到後端。對於經常在上線和離線狀態之間切換的用戶端應用程式而言，這種做法可讓效率大幅提升。

若要實作 Delta Sync，`Sync` 查詢會在已建立版本的資料來源上使用 `Sync` 操作。當 an AWS AppSync 變動變更版本控制資料來源中的項目時，該變更的記錄也會存放在 *Delta* 資料表中。您可以選擇針對其他版本控制的資料來源使用不同的 *Delta* 資料表 （例如，每種類型一個，每個網域區域一個），或針對您的 API 使用單一 *Delta* 資料表。 AWS AppSync 建議不要針對多個 APIs 使用單一 *Delta* 資料表，以避免主要金鑰發生衝突。

此外，Delta Sync 用戶端也可以透過引數接收訂閱，然後用戶端協調訂閱和重新連線，並在離線和線上轉換之間進行寫入。Delta Sync 會自動恢復訂閱來執行這個程序，包括指數退避、透過不同的網路錯誤案例進行抖動，以及將事件存放在佇列中。接著會先執行適當的 delta 或 base 查詢，再合併佇列中的任何事件，最後依照一般方式處理訂閱。

[Amplify 架構網站](https://aws-amplify.github.io/)提供關於用戶端組態選項的文件，包括 Amplify DataStore。這份文件概述如何設定已建立版本的 DynamoDB 資料來源和 `Sync` 操作，以便與 Delta Sync 用戶端搭配使用，提供最佳的資料存取權。

## 一鍵設定
<a name="one-click-setup"></a>

若要在設定所有解析程式和必要 AWS 資源的情況下自動設定 GraphQL 端點 in AWS AppSync，請使用此 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/deltasync/deltasync-v2-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/deltasync/deltasync-v2-full.yaml) 

這個堆疊會在您的帳戶中建立下列資源：
+ 2 個 DynamoDB 資料表 (Base 和 Delta)
+ 1 AWS AppSync API 與 API 金鑰
+ 1 個具有 DynamoDB 資料表政策的 IAM 角色

兩個資料表會用來將同步查詢分割成第二個資料表，並做為用戶端離線時遺漏事件的日誌。為了讓 delta 資料表的查詢維持效率，您可以視需要使用 [Amazon DynamoDB TTLs](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) 來自動備妥這些事件。TTL 時間可根據您對資料來源的需求進行設定 (您可能希望將其設為 1 小時、1天等)。

## 結構描述
<a name="schema"></a>

為了示範 Delta Sync，範例應用程式會在 DynamoDB 中建立由 *Base* 和 *Delta* 資料表支援的 *Posts* 結構描述。 AWS AppSync 會自動將變動寫入兩個資料表。同步查詢會依適當情況從 *Base* 或 *Delta* 資料表提取記錄，並且定義一份訂閱，說明用戶端如何透過其重新連線邏輯來發揮變動功能。

```
input CreatePostInput {
    author: String!
    title: String!
    content: String!
    url: String
    ups: Int
    downs: Int
    _version: Int
}

interface Connection {
  nextToken: String
  startedAt: AWSTimestamp!
}

type Mutation {
    createPost(input: CreatePostInput!): Post
    updatePost(input: UpdatePostInput!): Post
    deletePost(input: DeletePostInput!): Post
}

type Post {
    id: ID!
    author: String!
    title: String!
    content: String!
    url: AWSURL
    ups: Int
    downs: Int
    _version: Int
    _deleted: Boolean
    _lastChangedAt: AWSTimestamp!
}

type PostConnection implements Connection {
    items: [Post!]!
    nextToken: String
    startedAt: AWSTimestamp!
}

type Query {
    getPost(id: ID!): Post
    syncPosts(limit: Int, nextToken: String, lastSync: AWSTimestamp): PostConnection!
}

type Subscription {
    onCreatePost: Post
        @aws_subscribe(mutations: ["createPost"])
    onUpdatePost: Post
        @aws_subscribe(mutations: ["updatePost"])
    onDeletePost: Post
        @aws_subscribe(mutations: ["deletePost"])
}

input DeletePostInput {
    id: ID!
    _version: Int!
}

input UpdatePostInput {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    _version: Int!
}

schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}
```

這個 GraphQL 結構描述雖然是標準程序，但在繼續之前，有幾個事項值得我們提出說明。首先，所有變動會先自動寫入 *Base* 資料表，然後寫入 *Delta* 資料表。*Base* 資料表是狀態實況的中心來源，而 *Delta* 資料表是日誌記錄。如果您未傳入 `lastSync: AWSTimestamp`，`syncPosts`查詢會針對 *Base* 資料表執行 ，並補充快取，以及在用戶端離線時間超過 *Delta* 資料表中設定的 TTL 時間時，定期執行 做為節點案例的*全域補齊程序*。如果您傳入 `lastSync: AWSTimestamp`，`syncPosts` 查詢會執行處理您的 *Delta* 資料表，並由用戶端用來擷取自其上次離線後有所變更的事件。Amplify 用戶端會自動傳遞 `lastSync: AWSTimestamp` 值，並依適當情況儲存到磁碟中。

*Post* 上的 *\$1deleted* 欄位用於 **DELETE** 操作。當用戶端離線且記錄已從 *Base* 資料表移除時，此屬性會通知用戶端執行同步處理，將記錄自其本機快取中移除。如果用戶端離線時間較長，而且在用戶端可以配合 Delta Sync 查詢來擷取這個值之前，該項目已先移除，則 Base 查詢 (可在用戶端中設定) 的全域更新事件將會執行，並從快取中移除該項目。此欄位會標記為選用項目，因為只有在執行同步查詢而結果出現已刪除的項目時，才會傳回值。

## 變動
<a name="mutations"></a>

對於所有變動， AWS AppSync 會在*基礎*資料表中執行標準Create/Update/Delete操作，也會自動在 *Delta* 資料表中記錄變更。您可以藉由在資料來源中修改 `DeltaSyncTableTTL` 值，以縮短或延長記錄保留時間。對於擁有高速資料的組織，應該會考慮縮短這段時間。或者，如果您的用戶端離線時間較長，最好設定較長的資料保留時間。

## 同步查詢
<a name="sync-queries"></a>

*基本查詢*是沒有指定`lastSync`值的 DynamoDB Sync 操作。這種做法適合許多組織，因為 Base 查詢只會在一開始時執行，且後續是定期執行。

*Delta 查詢*是指定`lastSync`值的 DynamoDB Sync 操作。每當離線用戶端恢復上線，就會執行 *delta 查詢* (只要 base 查詢定期時間未觸發執行)。用戶端會自動追蹤上次成功執行查詢以同步資料的時間。

執行 delta 查詢時，查詢的解析程式會使用 `ds_pk` 和 `ds_sk` 僅查詢自上次用戶端執行同步以來變更的記錄。用戶端會存放適當的 GraphQL 回應。

如需有關執行同步查詢的詳細資訊，請參閱[同步操作文件](aws-appsync-conflict-detection-and-sync-sync-operations.md)。

## 範例
<a name="example"></a>

首先，藉由呼叫 `createPost` 變動來建立一個項目：

```
mutation create {
  createPost(input: {author: "Nadia", title: "My First Post", content: "Hello World"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

此變動的傳回值將如下所示：

```
{
  "data": {
    "createPost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "My First Post",
      "content": "Hello World",
      "_version": 1,
      "_lastChangedAt": 1574469356331,
      "_deleted": null
    }
  }
}
```

如果您檢查 *Base* 資料表的內容，您會看到一筆記錄，如下所示：

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

如果您檢查 *Delta* 資料表的內容，您將會看見一筆記錄，如下所示：

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_ttl": {
    "N": "1574472956"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:35:56.331:81d36bbb-1579-4efe-92b8-2e3f679f628b:1"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

現在，我們可以模擬 *Base* 查詢，客戶端將使用以下 `syncPosts` 查詢來執行該查詢以填入其本機資料存放區：

```
query baseQuery {
  syncPosts(limit: 100, lastSync: null, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
      _lastChangedAt
    }
    startedAt
    nextToken
  }
}
```

此 *Base* 查詢的傳回值將如下所示：

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "My First Post",
          "content": "Hello World",
          "_version": 1,
          "_lastChangedAt": 1574469356331
        }
      ],
      "startedAt": 1574469602238,
      "nextToken": null
    }
  }
}
```

稍後，我們將儲存 `startedAt` 值，以模擬 *Delta* 查詢，但需要先對資料表進行變更。讓我們使用 `updatePost` 變動以修改現有的 Post：

```
mutation updatePost {
  updatePost(input: {id: "81d36bbb-1579-4efe-92b8-2e3f679f628b", _version: 1, title: "Actually this is my Second Post"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

此變動的傳回值將如下所示：

```
{
  "data": {
    "updatePost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "Actually this is my Second Post",
      "content": "Hello World",
      "_version": 2,
      "_lastChangedAt": 1574469851417,
      "_deleted": null
    }
  }
}
```

如果您現在檢查 *Base* 資料表的內容，您應該會看見更新的項目：

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

如果您現在檢查 *Delta* 資料表的內容，您應該會看到兩筆記錄：

1. 建立項目時的記錄

1. 更新項目時的記錄。

新項目如下所示：

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_ttl": {
    "N": "1574473451"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:44:11.417:81d36bbb-1579-4efe-92b8-2e3f679f628b:2"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

現在，我們可以模擬 *Delta* 查詢，以擷取用戶端離線時發生的修改。我們將使用基本**查詢傳回`startedAt`的值來提出請求：

```
query delta {
  syncPosts(limit: 100, lastSync: 1574469602238, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
    }
    startedAt
    nextToken
  }
}
```

此 *Delta* 查詢的傳回值將如下所示：

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "Actually this is my Second Post",
          "content": "Hello World",
          "_version": 2
        }
      ],
      "startedAt": 1574470400808,
      "nextToken": null
    }
  }
}
```