

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

# 使用管道解析程式 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"
    }
  ]
}
```

這表示您已成功使用管道解析程式，實作複雜的授權程序。