

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

# JavaScript 的解析器教程 AWS AppSync
<a name="tutorials-js"></a>

数据源和解析器用于翻译 GraphQL 请求并从 AWS 您的资源中获取信息。 AWS AppSync AWS AppSync 支持自动配置和与某些数据源类型的连接。 AWS AppSync 还支持 AWS Lambda Amazon DynamoDB、关系数据库（亚马逊 Aurora Serverless）、 OpenSearch 亚马逊服务和 HTTP 终端节点作为数据源。您可以将 GraphQL API 与现有 AWS 资源一起使用，也可以从头开始构建数据源和解析器。以下各节旨在以教程的形式阐明一些比较常见的 GraphQL 使用案例。

**Topics**
+ [使用 DynamoDB JavaScript 解析器创建简单的后期应用程序](tutorial-dynamodb-resolvers-js.md)
+ [使用 AWS Lambda 解析器](tutorial-lambda-resolvers-js.md)
+ [使用本地解析器](tutorial-local-resolvers-js.md)
+ [组合使用 GraphQL 解析器](tutorial-combining-graphql-resolvers-js.md)
+ [使用 OpenSearch 服务解析器](tutorial-elasticsearch-resolvers-js.md)
+ [执行 DynamoDB 事务](tutorial-dynamodb-transact-js.md)
+ [执行 DynamoDB 批处理操作](tutorial-dynamodb-batch-js.md)
+ [使用 HTTP 解析器](tutorial-http-resolvers-js.md)
+ [使用带有数据 API 的 Aurora PostgreSQL](aurora-serverless-tutorial-js.md)

# 使用 DynamoDB JavaScript 解析器创建简单的后期应用程序
<a name="tutorial-dynamodb-resolvers-js"></a>

在本教程中，您将导入您的 Amazon DynamoDB 表并将其连接起来， AWS AppSync 以便 JavaScript 使用可以在自己的应用程序中使用的管道解析器构建功能齐全的 GraphQL API。

您将使用 AWS AppSync 控制台配置您的 Amazon DynamoDB 资源、创建解析器并将其连接到您的数据源。您也可以通过 GraphQL 语句读取和写入 Amazon DynamoDB 数据库并订阅实时数据。

必须完成一些特定的步骤，才能将 GraphQL 语句转换为 Amazon DynamoDB 操作以及将响应转换回 GraphQL。本教程通过一些现实世界的场景和数据访问模式介绍了配置过程。

## 创建您的 GraphQL API
<a name="create-graphql-api"></a>

**要在中创建 GraphQL API AWS AppSync**

1. 打开 AppSync 控制台并选择**创建 API**。

1. 选择**从头开始设计**，然后选择**下一步**。

1. 将您的 API 命名为 `PostTutorialAPI`，然后选择**下一步**。跳到检查页面，同时将其余选项设置为默认值，然后选择`Create`。

 AWS AppSync 控制台会为您创建一个新的 GraphQL API。默认情况下，它使用 API 密钥身份验证方式。您可以根据本教程后面的说明，使用控制台设置 GraphQL API 的其余部分，并针对它运行查询。

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

您现在具有 GraphQL API，您可以设置一个基本架构，以允许对文章数据执行基本创建、检索和删除操作。

**将数据添加到您的架构**

1. 在您的 API 中，选择**架构**选项卡。

1. 我们将创建一个架构，它定义 `Post` 类型和 `addPost` 操作以添加和获取 `Post` 对象。在**架构**窗格中，将内容替换为以下代码：

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

1. 选择 **Save Schema (保存架构)**。

## 设置您的 Amazon DynamoDB 表
<a name="configure-dynamodb"></a>

 AWS AppSync 控制台可以帮助预配置将您自己的 AWS 资源存储在 Amazon DynamoDB 表中所需的资源。在该步骤中，您创建一个 Amazon DynamoDB 表以存储您的文章。您还会设置我们稍后使用的[二级索引](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html)。

**创建您的 Amazon DynamoDB 表**

1. 在**架构**页面上，选择**创建资源**。

1. 选择**使用现有的类型**，然后选择 `Post` 类型。

1. 在**其他索引**部分中，选择**添加索引**。

1. 将索引命名为 `author-index`。

1. 将 `Primary key` 设置为 `author`，并将 `Sort` 键设置为 `None`。

1. 禁用**自动生成 GraphQL**。在该示例中，我们将自行创建解析器。

1. 选择**创建**。

您现在具有一个名为 `PostTable` 的新数据来源，您可以访问侧面选项卡中的**数据来源**以查看该数据来源。您使用该数据来源将查询和变更链接到 Amazon DynamoDB 表。

## 设置 AddPost 解析器 (亚马逊 DynamoDB) PutItem
<a name="configure-addpost"></a>

现在已经知道 AWS AppSync 了 Amazon DynamoDB 表，您可以通过定义解析器将其链接到各个查询和变更。您创建的第一个解析器是使用的`addPost`管道解析器 JavaScript，它允许您在 Amazon DynamoDB 表中创建帖子。管道解析器具有以下组件：
+ GraphQL 架构中的位置，用于附加解析器。在本例中，您将设置 `createPost` 类型的 `Mutation` 字段的解析器。在调用方调用 `{ addPost(...){...} }` 变更时，将调用该解析器。
+ 此解析器所用的数据来源。在该示例中，您希望使用以前定义的 DynamoDB 数据来源，因此，您可以在 `post-table-for-tutorial` DynamoDB 表中添加条目。
+ 请求处理程序。请求处理程序是一个函数，用于处理来自调用者的传入请求，并将其转换为 AWS AppSync 要对 DynamoDB 执行的指令。
+ 响应处理程序。响应处理程序的任务是，处理来自 DynamoDB 的响应，并将其转换回 GraphQL 所需的内容。如果 DynamoDB 中的数据形态与 GraphQL 中的 `Post` 类型不同，此模板很有用。但在此例中它们的形态相同，所以只用于传递数据。

**设置您的解析器**

1. 在您的 API 中，选择**架构**选项卡。

1. 在**解析器**窗格中，找到 `Mutation` 类型下面的 `addPost` 字段，然后选择**附加**。

1. 选择您的数据来源，然后选择**创建**。

1. 在代码编辑器中，将代码替换为以下代码片段：

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

1. 选择**保存**。

**注意**  
在该代码中，您使用 DynamoDB 模块实用程序轻松创建 DynamoDB 请求。

AWS AppSync 附带一个名为的自动生成ID的实用程序`util.autoId()`，用于为您的新帖子生成ID。如果您未指定 ID，该实用程序将自动为您生成 ID。

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

有关可用实用程序的更多信息 JavaScript，请参阅[解析器和函数的JavaScript运行时功能](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html)。

### 调用 API 以添加文章
<a name="call-api-addpost"></a>

现在，解析器已经配置完毕， AWS AppSync 可以将传入的`addPost`突变转换为 Amazon DynamoDB 操作。`PutItem`现在，您可以运行一个变更，在表中添加内容。

**运行操作**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下变更：

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

1. 选择**运行**（橙色播放按钮），然后选择 `addPost`。新创建的文章的结果应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

以下解释说明了发生的情况：

1. AWS AppSync 收到了`addPost`变异请求。

1. AWS AppSync 执行解析器的请求处理程序。`ddb.put` 函数创建一个 `PutItem` 请求，如下所示：

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

1. AWS AppSync 使用此值生成和执行 Amazon DynamoDB 请求`PutItem`。

1. AWS AppSync 获取`PutItem`请求的结果并将其转换回 GraphQL 类型。

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

1. 响应处理程序立即返回结果 (`return ctx.result`)。

1. 最终结果显示在 GraphQL 响应中。

## 设置 GetPost 解析器（亚马逊 DynamoDB） GetItem
<a name="configure-getpost"></a>

您现在能够将数据添加到 Amazon DynamoDB 表中，您需要设置 `getPost` 查询，以使其可以从表中检索该数据。为了实现此目的，您要设置另一解析器。

**添加您的解析器**

1. 在您的 API 中，选择**架构**选项卡。

1. 在右侧的**解析器**窗格中，找到 `Query` 类型上的 `getPost` 字段，然后选择**附加**。

1. 选择您的数据来源，然后选择**创建**。

1. 在代码编辑器中，将代码替换为以下代码片段：

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

1. 保存解析器。

**注意**  
在该解析器中，我们将箭头函数表达式用于响应处理程序。

### 调用 API 以获取文章
<a name="call-api-getpost"></a>

现在，解析器已经设置完毕， AWS AppSync 知道如何将传入的`getPost`查询转换为 Amazon DynamoDB `GetItem` 操作。现在，您可以运行查询，检索之前创建的文章。

**运行您的查询**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下代码，并使用您在创建文章后复制的 ID：

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

1. 选择**运行**（橙色播放按钮），然后选择 `getPost`。新创建的文章的结果应显示在**查询**窗格右侧的**结果**窗格中。

1. 从 Amazon DynamoDB 检索的文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

或者，采用以下示例：

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

如果您的 `getPost` 查询仅需要 `id`、`author` 和 `title`，您可以将请求函数更改为使用投影表达式仅指定您希望从 DynamoDB 表中获取的属性，以避免将不必要的数据从 DynamoDB 传输到 AWS AppSync。例如，请求函数可能类似于以下代码片段：

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

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

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

您也可以使用w [selectionSetList](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference-js.html#aws-appsync-resolver-context-reference-info-js)it `getPost` h来表示`expression`：

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

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

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

## 创建 UpdatePost 突变 (亚马逊 DynamoDB) UpdateItem
<a name="configure-updatepost"></a>

到目前为止，您可以在 Amazon DynamoDB 中创建和检索 `Post` 对象。接下来，您设置一个新的变更以更新对象。与需要指定所有字段的 `addPost` 变更相比，该变更允许您仅指定要更改的字段。它还引入一个新的 `expectedVersion` 参数，以允许您指定要修改的版本。您设置一个条件，以确保您修改对象的最新版本。您将使用 `UpdateItem` Amazon DynamoDB 操作完成该操作。

**更新您的解析器**

1. 在您的 API 中，选择**架构**选项卡。

1. 在 **Schema (架构)** 窗格中修改 `Mutation` 类型，添加新的 `updatePost` 变更，如下所示：

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

1. 选择 **Save Schema (保存架构)**。

1. 在右侧的**解析器**窗格中，找到 `Mutation` 类型上的新创建的 `updatePost` 字段，然后选择**附加**。使用下面的代码片段创建新的解析器：

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

1. 保存您所做的任何更改。

该解析器使用 `ddb.update` 创建 Amazon DynamoDB `UpdateItem` 请求。您仅要求 Amazon DynamoDB 更新某些属性，而不是编写整个项目。这是使用 Amazon DynamoDB 更新表达式完成的。

`ddb.update` 函数将一个键和更新对象作为参数。然后，您检查传入的参数的值。在一个值设置为 `null` 时，使用 DynamoDB `remove` 操作指示应从 DynamoDB 项目中删除该值。

还有一个新的 `condition` 部分。条件表达式允许您在执行操作之前根据已存在于 Amazon DynamoDB 中的对象的状态来告诉 AWS AppSync Amazon DynamoDB 请求是否应该成功。在该示例中，只有在当前位于 Amazon DynamoDB 中的项目的 `version` 字段与 `expectedVersion` 参数完全匹配时，您才希望 `UpdateItem` 请求成功。在更新项目时，我们希望增加 `version` 的值。可以使用 `increment` 操作函数轻松完成该操作。

有关条件表达式的更多信息，请参阅[条件表达式](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-condition-expressions)文档。

有关该`UpdateItem`请求的更多信息，请参阅[UpdateItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-updateitem)文档和 [DynamoDB 模块](https://docs.aws.amazon.com/appsync/latest/devguide/built-in-modules-js.html)文档。

有关如何编写更新表达式的更多信息，请参阅 [DynamoDB 文档 UpdateExpressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)。

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

让我们尝试使用新的解析器更新 `Post` 对象。

**更新您的对象**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下变更。您还需要将 `id` 参数更新为您以前记下的值：

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

1. 选择**运行**（橙色播放按钮），然后选择 `updatePost`。

1. 在 Amazon DynamoDB 中更新的文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

在此请求中，您要求 AWS AppSync 和亚马逊 DynamoDB 仅更新和字段`title`。`content`所有其他字段保持不变（除了递增 `version` 字段以外）。您将 `title` 属性设置为新的值，并从文章中删除 `content` 属性。`author`、`url`、`ups` 和 `downs` 字段没有变化。再次尝试执行变更请求，同时将请求完全保持原样。您可以看到类似以下内容的响应：

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

请求失败，因为条件表达式的评估结果为 `false`：

1. 第一次运行请求时，Amazon DynamoDB 中的文章的 `version` 字段值为 `1`，它与 `expectedVersion` 参数匹配。请求成功，这意味着 Amazon DynamoDB 中的 `version` 字段已增加到 `2`。

1. 第二次运行请求时，Amazon DynamoDB 中的文章的 `version` 字段值为 `2`，它与 `expectedVersion` 参数不匹配。

这种模式通常被称为*乐观锁*。

## 创建投票突变 (亚马逊 DynamoDB) UpdateItem
<a name="configure-vote-mutations"></a>

`Post` 类型包含 `ups` 和 `downs` 字段，用于记录点赞和差评。不过，API 目前不允许我们进行任何评论。让我们添加一个变更，以对文章点赞和差评。

**添加您的变更**

1. 在您的 API 中，选择**架构**选项卡。

1. 在**架构**窗格中，修改 `Mutation` 类型并添加 `DIRECTION` 枚举以添加新的评论变更：

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

1. 选择 **Save Schema (保存架构)**。

1. 在右侧的**解析器**窗格中，找到 `Mutation` 类型上的新创建的 `vote` 字段，然后选择**附加**。创建代码并将其替换为以下代码片段，以创建新的解析器：

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

1. 保存您所做的任何更改。

### 调用 API 以对文章点赞或差评
<a name="call-api-vote"></a>

现在，新的解析器已经设置完毕，他们 AWS AppSync 知道如何将传入`upvotePost`或`downvote`突变转换为 Amazon DynamoDB 操作。`UpdateItem`现在您可以运行变更，为之前创建的文章点赞或差评。

**运行您的变更**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下变更。您还需要将 `id` 参数更新为您以前记下的值：

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

1. 选择**运行**（橙色播放按钮），然后选择 `votePost`。

1. 在 Amazon DynamoDB 中更新的文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

1. 再选择几次**运行**。每次执行查询时，您应该会看到 `ups` 和 `version` 字段增加 `1`。

1. 更改查询以使用不同的 `DIRECTION` 进行调用。

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

1. 选择**运行**（橙色播放按钮），然后选择 `votePost`。

   这次，每次运行查询时，您应该会看到 `downs` 和 `version` 字段增加 `1`。

## 设置 DeletePost 解析器（亚马逊 DynamoDB） DeleteItem
<a name="configure-deletepost"></a>

接下来，您希望创建一个变更以删除文章。您将使用 `DeleteItem` Amazon DynamoDB 操作完成该操作。

**添加您的变更**

1. 在您的架构中，选择**架构**选项卡。

1. 在**架构**窗格中，修改 `Mutation` 类型以添加新的 `deletePost` 变更：

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

1. 这次，您将 `expectedVersion` 字段设置为可选。接下来，选择**保存架构**。

1. 在右侧的**解析器**窗格中，找到 `Mutation` 类型中的新创建的 `delete` 字段，然后选择**附加**。使用以下代码创建一个新的解析器：

   ```
   import { util } from '@aws-appsync/utils'
   
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     let condition = null;
     if (ctx.args.expectedVersion) {
       condition = {
         or: [
           { id: { attributeExists: false } },
           { version: { eq: ctx.args.expectedVersion } },
         ],
       };
     }
     return ddb.remove({ key: { id: ctx.args.id }, condition });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   }
   ```
**注意**  
`expectedVersion` 参数是可选的参数。如果调用方在请求中设置 `expectedVersion` 参数，请求处理程序将添加一个条件，只有在已删除项目或 Amazon DynamoDB 中的文章的 `version` 属性与 `expectedVersion` 完全匹配时，才允许 `DeleteItem` 请求成功。如果未设置此参数，则 `DeleteItem` 请求中不指定条件表达式。无论 `version` 值如何，或者项目在 Amazon DynamoDB 中是否存在，该请求都会成功。  
即使您要删除一个项目，如果尚未删除，您也可以返回要删除的项目。

有关该`DeleteItem`请求的更多信息，请参阅[DeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-deleteitem)文档。

### 调用 API 以删除文章
<a name="call-api-delete"></a>

现在解析器已经设置好了，我 AWS AppSync 知道如何将传入的`delete`突变转换为 Amazon DynamoDB 操作。`DeleteItem`现在，您可以运行变更，从表中删除一些内容。

**运行您的变更**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下变更。您还需要将 `id` 参数更新为您以前记下的值：

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

1. 选择**运行**（橙色播放按钮），然后选择 `deletePost`。

1. 将从 Amazon DynamoDB 中删除该文章。****请注意， AWS AppSync 返回的是从 Amazon DynamoDB 中删除的项目的值，该值应显示在 “查询” 窗格右侧的 “结果” 窗格中。****如下所示：

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

1. 只有在对 `deletePost` 的该调用实际将其从 Amazon DynamoDB 中删除时，才会返回该值。再次选择**运行**。

1. 调用仍然成功，但没有返回任何值：

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

1. 现在，让我们尝试删除一篇文章，但这次指定 `expectedValue`。首先，您需要创建一个新文章，因为您刚刚删除了迄今为止一直使用的文章。

1. 在**查询**窗格中，添加以下变更：

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

1. 选择**运行**（橙色播放按钮），然后选择 `addPost`。

1. 新创建的文章的结果应显示在**查询**窗格右侧的**结果**窗格中。记下新创建的对象的 `id`，因为您稍后需要使用该 ID。如下所示：

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

1. 现在，让我们尝试删除具有非法 **expectedVersion** 值的文章。在**查询**窗格中，添加以下变更。您还需要将 `id` 参数更新为您以前记下的值：

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

1. 选择**运行**（橙色播放按钮），然后选择 `deletePost`。将返回以下结果：

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

1. 请求失败，因为条件表达式的评估结果为 `false`。Amazon DynamoDB 中的文章 `version` 值与参数中指定的 `expectedValue` 不匹配。对象的当前值返回到 GraphQL 响应的 `data` 部分的 `errors` 字段中。重试请求，但更正 `expectedVersion`：

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

1. 选择**运行**（橙色播放按钮），然后选择 `deletePost`。

   这次请求成功，并返回从 Amazon DynamoDB 中删除的值：

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

1. 再次选择**运行**。调用仍然成功，但这次没有返回任何值，因为已在 Amazon DynamoDB 中删除该文章。

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

## 设置 allPost 解析器 (Amazon DynamoDB Scan)
<a name="configure-allpost"></a>

到目前为止，只有在您知道要查看的每篇文章的 `id` 时，才能使用该 API。让我们添加新的解析器，它可以返回表中的所有文章。

**添加您的变更**

1. 在您的 API 中，选择**架构**选项卡。

1. 在 **Schema (架构)** 窗格中修改 `Query` 类型，添加新的 `allPost` 查询，如下所示：

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

1. 添加新 `PaginationPosts` 类型：

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

1. 选择 **Save Schema (保存架构)**。

1. 在右侧的**解析器**窗格中，找到 `Query` 类型中的新创建的 `allPost` 字段，然后选择**附加**。使用以下代码创建一个新的解析器：

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

   该解析器的请求处理程序需要使用两个可选的参数：
   + `limit` - 指定单次调用中返回的最大项目数。
   + `nextToken` - 用于检索下一组结果（我们稍后将显示 `nextToken` 值来自何处）。

1. 保存对您的解析器所做的任何更改。

有关 `Scan` 请求的更多信息，请参阅 [Scan](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-scan) 参考文档。

### 调用 API 以扫描所有文章
<a name="call-api-scan"></a>

现在，解析器已经设置完毕， AWS AppSync 知道如何将传入的`allPost`查询转换为 Amazon DynamoDB `Scan` 操作。现在您可以扫描整个表，检索所有文章。在进行尝试之前，您需要在表中填充一些数据，因为您已经删除了之前使用的所有内容。

**添加和查询数据**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下变更：

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

1. 选择**运行**（橙色播放按钮）。

1. 现在，让我们扫描表，每次返回 5 个结果。在**查询**窗格中，添加以下查询：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPost`。

   前 5 篇文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

1. 您收到 5 个结果和一个 `nextToken`（可用于获取下一组结果）。更新 `allPost` 查询，加入上一组结果的 `nextToken`：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPost`。

   其余 4 篇文章应显示在**查询**窗格右侧的**结果**窗格中。在这组结果中没有 `nextToken`，因为您已查看了所有 9 篇文章，没有其余文章了。如下所示：

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

## 设置 allPostsBy作者解析器（亚马逊 DynamoDB 查询）
<a name="configure-query"></a>

除了扫描 Amazon DynamoDB 以查找所有文章以外，您还可以查询 Amazon DynamoDB 以检索特定作者创建的文章。您以前创建的 Amazon DynamoDB 表已具有一个名为 `author-index` 的 `GlobalSecondaryIndex`，您可以将其与 Amazon DynamoDB `Query` 操作一起使用以检索特定作者创建的所有文章。

**添加您的查询**

1. 在您的 API 中，选择**架构**选项卡。

1. 在 **Schema (架构)** 窗格中修改 `Query` 类型，添加新的 `allPostsByAuthor` 查询，如下所示：

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

   请注意，它使用与 `allPost` 查询相同的 `PaginatedPosts` 类型。

1. 选择 **Save Schema (保存架构)**。

1. 在右侧的**解析器**窗格中，找到 `Query` 类型上的新创建的 `allPostsByAuthor` 字段，然后选择**附加**。使用下面的代码片段创建一个解析器：

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

   与 `allPost` 解析器一样，该解析器具有两个可选的参数：
   + `limit` - 指定单次调用中返回的最大项目数。
   + `nextToken` - 检索下一组结果（可以从以前的调用中获取 `nextToken` 值）。

1. 保存对您的解析器所做的任何更改。

有关 `Query` 请求的更多信息，请参阅 [Query](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-query) 参考文档。

### 调用 API 以查询作者的所有文章
<a name="call-api-query"></a>

现在解析器已经设置好了，我 AWS AppSync 知道如何将传入的`allPostsByAuthor`突变转换为针对索引的 DynamoDB 操作`Query`。`author-index`现在，您可以查询表，检索某一作者的所有文章。

不过，在此之前，让我们在表中再填充一些文章，因为到目前为止每篇文章都是由同一作者撰写的。

**添加数据和查询**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下变更：

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

1. 选择**运行**（橙色播放按钮），然后选择 `addPost`。

1. 现在，让我们查询表，返回作者为 `Nadia` 的所有文章。在**查询**窗格中，添加以下查询：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPostsByAuthor`。`Nadia` 撰写的所有文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

1. `Query` 的分页方式与 `Scan` 相同。例如，如果我们查找作者为 `AUTHORNAME` 的所有文章，每次显示 5 个结果。

1. 在**查询**窗格中，添加以下查询：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPostsByAuthor`。`AUTHORNAME` 撰写的所有文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

1. 用上次查询返回的值更新 `nextToken` 参数，如下所示：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPostsByAuthor`。`AUTHORNAME` 撰写的其余文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

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

到目前为止，该`Post`类型一直是一个平面 key/value 对象。您也可以使用解析器对复杂对象进行建模，例如集、列表和映射。让我们更新 `Post` 类型，加入标签。一篇文章可以具有 0 个或更多标签，这些标签作为字符串集存储在 DynamoDB 中。您还将设置一些变更，用于添加并删除标签；还要用一个新查询扫描具有特定标签的文章。

**设置您的数据**

1. 在您的 API 中，选择**架构**选项卡。

1. 在 **Schema (架构)** 窗格中修改 `Post` 类型，添加新的 `tags` 字段，如下所示：

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

1. 在 **Schema (架构)** 窗格中修改 `Query` 类型，添加新的 `allPostsByTag` 查询，如下所示：

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

1. 在 **Schema (架构)** 窗格中修改 `Mutation` 类型，添加新的 `addTag` 和 `removeTag` 变更，如下所示：

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

1. 选择 **Save Schema (保存架构)**。

1. 在右侧的**解析器**窗格中，找到 `Query` 类型上的新创建的 `allPostsByTag` 字段，然后选择**附加**。使用下面的代码片段创建您的解析器：

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

1. 保存对您的解析器所做的任何更改。

1. 现在，使用下面的代码片段为 `Mutation` 字段 `addTag` 执行相同的操作：
**注意**  
尽管 DynamoDB 实用程序当前不支持集操作，但您仍然可以自行构建请求以与集进行交互。

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

1. 保存对您的解析器所做的任何更改。

1. 使用下面的代码片段为 `Mutation` 字段 `removeTag` 再重复一次该操作：

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

1. 保存对您的解析器所做的任何更改。

### 调用 API 以处理标签
<a name="call-api-tags"></a>

现在您已经设置了解析器， AWS AppSync 知道如何将传入的`addTag``removeTag`、和`allPostsByTag`请求转换为 D `UpdateItem` ynamo `Scan` DB 和操作。我们选择您之前创建的一个文章进行尝试。例如，我们使用作者为 `Nadia` 的一篇文章。

**使用标签**

1. 在您的 API 中，选择**查询**选项卡。

1. 在**查询**窗格中，添加以下查询：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPostsByAuthor`。

1. Nadia 的所有文章应显示在**查询**窗格右侧的**结果**窗格中。如下所示：

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

1. 让我们使用标题为 *The cutest dog in the world* 的文章。记下其 `id`，因为您稍后使用该 ID。现在，让我们尝试添加一个 `dog` 标签。

1. 在**查询**窗格中，添加以下变更。您还需要将 `id` 参数更新为您以前记下的值。

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

1. 选择**运行**（橙色播放按钮），然后选择 `addTag`。将使用新标签更新该文章：

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

1. 您可以添加更多标签。更新变更以将 `tag` 参数更改为 `puppy`：

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

1. 选择**运行**（橙色播放按钮），然后选择 `addTag`。将使用新标签更新该文章：

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

1. 您也可以删除标签。在**查询**窗格中，添加以下变更。您还需要将 `id` 参数更新为您以前记下的值：

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

1. 选择**运行**（橙色播放按钮），然后选择 `removeTag`。文章已更新，`puppy` 标签已删除。

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

1. 您也可以搜索所有具有标签的文章。在**查询**窗格中，添加以下查询：

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

1. 选择**运行**（橙色播放按钮），然后选择 `allPostsByTag`。将返回具有 `dog` 标签的所有文章，如下所示：

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

## 结论
<a name="conclusion-dynamodb-tutorial-js"></a>

在本教程中，您构建了一个 API，可用于通过 AWS AppSync 和 GraphQL 处理 DynamoDB 中的 `Post` 对象。

要进行清理，您可以从控制台中删除 AWS AppSync GraphQL API。

要删除与您的 DynamoDB 表关联的角色，请在**数据来源**表中选择您的数据来源，然后单击**编辑**。记下**创建或使用现有角色**下面的角色值。转到 IAM 控制台以删除该角色。

要删除您的 DynamoDB 表，请在数据来源列表中单击该表名称。这会转到 DynamoDB 控制台，您可以在其中删除该表。

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

你可以 AWS Lambda 和一起使用 AWS AppSync 来解析任何 GraphQL 字段。例如，GraphQL 查询可能会向 Amazon Relational Database Service (Amazon RDS) 实例发送调用，而 GraphQL 变更可能会写入到 Amazon Kinesis 流。在本节中，我们说明了如何编写 Lambda 函数，以根据 GraphQL 字段操作调用执行业务逻辑。

## 电动工具适用于 AWS Lambda
<a name="powertools-graphql"></a>

适用于 AWS Lambda GraphQL 的 Powertools 事件处理程序简化了 Lambda 函数中 GraphQL 事件的路由和处理。事件处理程序适用于 Python 和 Typescript。在 Powertools for AWS Lambda 文档中阅读有关 GraphQL API 事件处理程序的更多信息，请参见以下参考资料。
+ [适用于 AWS Lambda GraphQL 事件处理程序的 Powertools (Python)](https://docs.aws.amazon.com/powertools/python/latest/core/event_handler/appsync/)
+ [适用于 AWS Lambda GraphQL 事件处理程序的 Powertools（打字稿）](https://docs.aws.amazon.com/powertools/typescript/latest/features/event-handler/appsync-graphql/) 

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

以下示例显示了一个使用 `Node.js`（运行时系统：Node.js 18.x）编写的 Lambda 函数，该函数对博客文章应用程序包含的博客文章执行各种操作。请注意，代码应保存在具有 .mis 扩展名的文件中。

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

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

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

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

该 Lambda 函数按 ID 检索文章，添加文章，检索文章列表以及获取给定文章的相关文章。

**注意**  
Lambda 函数对 `event.field` 执行 `switch` 语句以确定当前解析的字段。

使用 AWS 管理控制台创建此 Lambda 函数。

## 为 Lambda 配置数据来源
<a name="configure-data-source-for-lamlong-js"></a>

在创建 Lambda 函数后，在 AWS AppSync 控制台中导航到您的 GraphQL API，然后选择**数据来源**选项卡。

选择**创建数据来源**，输入友好的**数据来源名称**（例如 **Lambda**），然后为**数据来源类型**选择 **AWS Lambda 函数**。对于**区域**，选择与您的函数相同的区域。对于**函数 ARN**，选择您的 Lambda 函数的 Amazon 资源名称 (ARN)。

选择 Lambda 函数后，您可以创建一个新 AWS Identity and Access Management (IAM) 角色（为其 AWS AppSync 分配相应的权限），也可以选择具有以下内联策略的现有角色：

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

****  

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

------

您还必须为 IAM 角色建立信任关系 AWS AppSync ，如下所示：

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

****  

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

------

## 创建 GraphQL 架构
<a name="creating-a-graphql-schema-js"></a>

数据来源现已连接到您的 Lambda 函数，请创建 GraphQL 架构。

在 AWS AppSync 控制台的架构编辑器中，确保您的架构与以下架构相匹配：

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

## 配置解析器
<a name="configuring-resolvers-js"></a>

您现已注册了 Lambda 数据来源和有效的 GraphQL 架构，您可以使用解析器将 GraphQL 字段连接到 Lambda 数据来源。

您将创建一个使用 AWS AppSync JavaScript (`APPSYNC_JS`) 运行时并与您的 Lambda 函数交互的解析器。要了解有关使用编写 AWS AppSync 解析器和函数的更多信息 JavaScript，请参阅[解析器和函数的JavaScript 运行时功能](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html)。

有关 Lambda 映射模板的更多信息，请参阅 Lambda 的[JavaScript 解析器函数参考。](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-lambda-js.html)

在该步骤中，您将一个解析器附加到以下字段的 Lambda 函数：`getPost(id:ID!): Post`、`allPosts: [Post]`、`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` 和 `Post.relatedPosts: [Post]`。在 AWS AppSync 控制台的**架构**编辑器中，在**解析**器窗格中，选择`getPost(id:ID!): Post`字段旁边的**附加**。选择您的 Lambda 数据来源。接下来，提供以下代码：

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

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

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

在调用 Lambda 函数时，该解析器代码将字段名称、参数列表以及有关源对象的上下文传递给该函数。选择**保存**。

您已成功附加了您的首个解析器。对于其余字段，重复该操作。

## 测试您的 GraphQL API
<a name="testing-your-graphql-api-js"></a>

现在您的 Lambda 函数已与 GraphQL 解析器连接，您可以使用控制台或客户端应用程序运行一些变更和查询。

在 AWS AppSync 控制台的左侧，选择 **Quer** ies，然后粘贴以下代码：

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

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

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

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

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

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

## 返回错误
<a name="returning-errors-js"></a>

解析任何给定的字段可能会导致错误。使用 AWS AppSync，您可以从以下来源引发错误：
+ 解析器响应处理程序
+ Lambda 函数

### 从解析器响应处理程序中
<a name="from-the-resolver-response-handler-js"></a>

要故意引发错误，您可以使用 `util.error` 实用程序方法。它将 `errorMessage`、`errorType` 和可选的 `data` 值作为参数。出现错误后，`data` 对于将额外的数据返回客户端很有用。在 GraphQL 最终响应中，`data` 对象将添加到 `errors`。

以下示例说明了如何在 `Post.relatedPosts: [Post]` 解析器响应处理程序中使用该方法。

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

这将生成与以下内容类似的 GraphQL 响应：

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

由于出现错误，导致 `allPosts[0].relatedPosts` 为 *null*，而 `data.errors[0]` 对象中存在 `errorMessage`、`errorType` 和 `data`。

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

AWS AppSync 还可以理解 Lambda 函数引发的错误。Lambda 编程模型允许您引发*处理的* 错误。如果 Lambda 函数抛出错误， AWS AppSync 则无法解析当前字段。仅在响应中设置从 Lambda 返回的错误消息。目前，您无法通过从 Lambda 函数中引发错误，将任何无关数据传回到客户端。

**注意**  
如果您的 Lambda 函数引发了*未处理*的错误，则 AWS AppSync 使用 Lambda 设置的错误消息。

以下 Lambda 函数会引发错误：

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

在您的响应处理程序中收到错误。您可以使用 `util.appendError` 将错误附加到 GraphQL 的响应，以将其发回到响应。为此，请将您的 AWS AppSync函数响应处理程序更改为：

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

这将返回与以下内容类似的 GraphQL 响应：

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

## 高级使用案例：批处理
<a name="advanced-use-case-batching-js"></a>

该示例中的 Lambda 函数具有一个 `relatedPosts` 字段，它返回给定文章的相关文章列表。在示例查询中，从 Lambda 函数中调用 `allPosts` 字段将返回 5 篇文章。由于我们指定还希望为每个返回的文章解析 `relatedPosts`，因此，将 `relatedPosts` 字段操作调用 5 次。

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

虽然这在该特定示例中听起来可能并不严重，但这种累积的过度获取可能会迅速降低应用程序的性能。

如果您要针对同一查询中返回的相关 `Posts` 再次提取 `relatedPosts`，调用数量将显著增加。

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

在这个相对简单的查询中， AWS AppSync 将调用 Lambda 函数 1 \$1 5 \$1 25 = 31 次。

这是相当常见的挑战，常被称为 N\$11 问题（在本例中 N = 5），会导致延迟增加，以及应用程序费用提升。

我们解决此问题的方式是批处理类似的字段解析器请求。在该示例中，Lambda 函数可能会解析给定批次的文章的相关文章列表，而不是让 Lambda 函数解析单个给定文章的相关文章列表。

为了说明这一点，让我们更新 `relatedPosts` 的解析器以进行批处理。

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

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

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

现在，在解析的 `fieldName` 为 `relatedPosts` 时，代码将操作从 `Invoke` 更改为 `BatchInvoke`。现在，在**配置批处理**部分中为函数启用批处理。将最大批处理大小设置为 `5`。选择**保存**。

通过进行该更改，在解析 `relatedPosts` 时，Lambda 函数收到以下内容以作为输入：

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

如果在请求中指定了 `BatchInvoke`，Lambda 函数将收到请求列表并返回结果列表。

具体而言，结果列表必须与请求有效载荷条目的大小和顺序相匹配，这样 AWS AppSync 才能相应地匹配结果。

在该批处理示例中，Lambda 函数返回一批结果，如下所示：

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

您可以更新 Lambda 代码以处理 `relatedPosts` 批处理：

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

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

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

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

### 返回单个错误
<a name="returning-individual-errors-js"></a>

以前的示例表明，可以从 Lambda 函数中返回单个错误，或者从响应处理程序中引发错误。对于批处理调用，从 Lambda 函数中引发错误会将整个批次标记为失败。对于发生不可恢复错误的特定场景（例如，到数据存储的连接失败），这可能是可以接受的。不过，如果批次中的某些项目成功，而其他项目失败，则可能会同时返回错误和有效的数据。由于 AWS AppSync 需要批量响应才能列出与批次原始大小相匹配的元素，因此您必须定义一种能够区分有效数据和错误的数据结构。

例如，如果 Lambda 函数预计返回一批相关文章，您可以选择返回 `Response` 对象列表，其中每个对象具有可选的 *data*、*errorMessage* 和 *errorType* 字段。如果出现 *errorMessage* 字段，则表示出现错误。

以下代码说明了如何更新 Lambda 函数：

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

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

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

更新 `relatedPosts` 解析器代码：

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

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

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

响应处理程序现在检查 Lambda 函数在 `Invoke` 操作中返回的错误，检查 `BatchInvoke` 操作为各个项目返回的错误，最后检查 `fieldName`。对于 `relatedPosts`，该函数返回 `result.data`。对于所有其他字段，该函数仅返回 `result`。例如，请参阅下面的查询：

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

该查询返回类似下面的 GraphQL 响应：

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

### 配置最大批处理大小
<a name="configure-max-batch-size-js"></a>

要在解析器上配置最大批处理大小，请在 AWS Command Line Interface ()AWS CLI中使用以下命令：

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

**注意**  
在提供请求映射模板时，您必须使用 `BatchInvoke` 操作才能使用批处理。

# 在 AWS AppSync 中使用本地解析器
<a name="tutorial-local-resolvers-js"></a>

AWS AppSync 允许您使用支持的数据来源（AWS Lambda、Amazon DynamoDB 或 Amazon OpenSearch Service）执行各种操作。但在某些情况下，可能不必调用支持的数据来源。

这时本地解析器就很方便。本地解析器仅将请求处理程序结果**转发**到响应处理程序，而不会调用远程数据来源。字段解析是在 AWS AppSync 中完成的。

在很多情况下，本地解析器是非常有用的。最常用的使用案例是在不触发数据来源调用的情况下发布通知。为了说明该使用案例，让我们构建一个 pub/sub 应用程序，用户可以在其中发布和订阅消息。此示例利用了*订阅*，如果您不熟悉*订阅*，可以参考[实时数据](aws-appsync-real-time-data.md)教程。

## 创建 pub/sub 应用程序
<a name="create-the-pub-sub-application-js"></a>

首先，创建一个空白的 GraphQL API，方法是选择**从头开始设计**选项，并在创建 GraphQL API 时配置可选的详细信息。

在我们的 pub/sub 应用程序中，客户端可以订阅和发布消息。每条发布的消息包含名称和数据。将以下内容添加到架构中：

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

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

type Query {
	getChannel: Channel
}

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

接下来，让我们将一个解析器附加到 `Mutation.publish` 字段。在**架构**窗格旁边的**解析器**窗格中，找到 `Mutation` 类型，找到 `publish(...): Channel` 字段，然后单击**附加**。

创建一个 *None* 数据来源，并将其命名为 *PageDataSource*。将其附加到您的解析器。

使用以下代码片段添加您的解析器实施：

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

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

确保您创建解析器并保存所做的更改。

## 发送和订阅消息
<a name="send-and-subscribe-to-messages-js"></a>

要让客户端接收消息，它们必须先订阅收件箱。

在**查询**窗格中，执行 `SubscribeToData` 订阅：

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

 每次调用 `publish` 变更时，只有在消息发送到 `channel` 订阅时，订阅者才会收到消息。让我们在**查询**窗格中试一下。当您的订阅仍在控制台中运行时，打开另一个控制台，并在**查询**窗格中运行以下请求：

**注意**  
我们在该示例中使用有效的 JSON 字符串。

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

结果类似下面这样：

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

我们刚刚说明了如何使用本地解析器，即，在不离开 AWS AppSync 服务的情况下发布和接收消息。

# 将 GraphQL 解析器组合在一起 AWS AppSync
<a name="tutorial-combining-graphql-resolvers-js"></a>

GraphQL 架构中的解析器和字段具有 1:1 的关系，具有很高的灵活性。由于数据来源是在解析器上独立于架构配置的，因此，您可以通过不同的数据来源解析或处理 GraphQL 类型，从而允许您混合使用和匹配架构以最佳方式满足您的需求。

以下场景说明了如何在架构中混合使用和匹配数据来源。在开始之前，您应该熟悉如何为 Amazon DynamoDB 和亚马逊 AWS Lambda服务配置数据源和解析器。 OpenSearch 

## 示例架构
<a name="example-schema-js"></a>

以下架构具有一个名为 `Post` 的类型，其中包含三个 `Query` 和 `Mutation` 操作：

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

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

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

在该示例中，总共有 6 个解析器，每个解析器都需要一个数据来源。解决此问题的一种方法是将它们连接到`Posts`名为的单个 Amazon DynamoDB 表，在该`AllPost`表中，字段运行扫描，字段运行查询（参[JavaScript见 DynamoDB 的解析器函数参考](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html)）。`searchPosts`但是，您并不局限于亚马逊 DynamoDB；有不同的数据源（如 Lambda OpenSearch 或服务）可以满足您的业务需求。

## 通过解析器更改数据
<a name="alter-data-through-resolvers-js"></a>

您可能需要从 AWS AppSync 数据源不直接支持的第三方数据库返回结果。在将数据返回到 API 客户端之前，您可能还必须对数据执行复杂的修改。这可能是由于数据类型格式不正确造成的（例如，客户端上的时间戳差异），也可能是由于处理向后兼容性问题造成的。在这种情况下，将 AWS Lambda 函数作为数据源连接到您的 AWS AppSync API 是合适的解决方案。为了说明起见，在以下示例中， AWS Lambda 函数操作从第三方数据存储中获取的数据：

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

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

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

这是一个完全有效的 Lambda 函数，可以附加到 GraphQL 架构中的 `AllPost` 字段，以便返回所有结果的任何查询都可以对顶/踩操作获得随机数字。

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

对于某些应用程序，您可以对 DynamoDB 执行变更或简单查找查询，并让后台进程将文档传输到服务。 OpenSearch 您可以简单地将`searchPosts`解析器附加到 OpenSearch 服务数据源，然后使用 GraphQL 查询返回搜索结果（来自源自 DynamoDB 的数据）。在应用程序中添加高级搜索操作（例如关键字、模糊字词匹配，甚至地理空间查找）时，这可能是非常强大的。可以通过 ETL 流程完成从 DynamoDB 传输数据的过程，也可以使用 Lambda 从 DynamoDB 进行流式传输。

要开始使用这些特定数据来源，请参阅我们的 [DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-resolvers-js.html) 和 [Lambda](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers-js.html) 教程。

例如，通过使用以前教程中的架构，以下变更将一个项目添加到 DynamoDB 中：

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

这会将数据写入 DynamoDB，然后 DynamoDB 通过 Lambda 将数据流式传输到 OpenSearch 亚马逊服务，然后您可以使用亚马逊服务按不同字段搜索帖子。例如，由于数据存储在 Amazon S OpenSearch ervice 中，因此您可以使用自由格式的文本（即使是空格）搜索作者字段或内容字段，如下所示：

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

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

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

由于数据直接写入到 DynamoDB 中，因此，您仍然可以使用 `allPost{...}` 和 `getPost{...}` 查询对表执行高效的列表或项目查找操作。该堆栈将以下示例代码用于 DynamoDB 流：

**注意**  
该 Python 代码是一个示例，并不适合在生产代码中使用。

```
import boto3
import requests
from requests_aws4auth import AWS4Auth

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

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

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

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

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

然后，您可以使用 DynamoDB 流将其附加到主键`id`为的 DynamoDB 表，并且对 DynamoDB 源的任何更改都将流式传输到您的服务域中。 OpenSearch 有关配置上述功能的更多信息，请参阅 [DynamoDB 流文档](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html)。

# 在中使用亚马逊 OpenSearch 服务解析器 AWS AppSync
<a name="tutorial-elasticsearch-resolvers-js"></a>

AWS AppSync 支持从您在自己的 AWS 账户中配置的域中使用 Amazon OpenSearch 服务，前提是这些域不存在于 VPC 中。预置域后，您可以使用数据来源连接这些域，此时，您可以在架构中配置一个解析器以执行 GraphQL 操作（如查询、变更和订阅）。本教程将引导您了解一些常见示例。

如需了解更多信息，请参阅我们的[JavaScript 解析器函数参考。 OpenSearch](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-elasticsearch-js.html)

## 创建新的 OpenSearch 服务域
<a name="create-a-new-es-domain-js"></a>

要开始使用本教程，您需要一个现有的 OpenSearch 服务域。如果您没有域，可以使用以下示例。请注意，创建 OpenSearch 服务域最多可能需要 15 分钟，然后才能继续将其与 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
```

您可以在自己的账户中在美国西部 2（俄勒冈）地区启动以下 AWS CloudFormation 堆栈： AWS 

 [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 服务配置数据源
<a name="configure-data-source-for-es-js"></a>

创建 OpenSearch 服务域后，导航到您的 AWS AppSync GraphQL API，然后选择 “**数据源**” 选项卡。选择 “**创建数据源**”，然后输入数据源的友好名称，例如 “*oss*”。然后，为**数据源类型**选择 **Amazon OpenSearch 域名**，选择相应的区域，您应该会看到您的 OpenSearch 服务域已列出。选择后，您可以创建一个新角色并 AWS AppSync 分配适合角色的权限，也可以选择具有以下内联策略的现有角色：

您还需要 AWS AppSync 为该角色与建立信任关系：

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

****  

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

------

此外， OpenSearch 服务域有自己的**访问策略**，您可以通过 Amazon S OpenSearch ervice 控制台对其进行修改。您必须添加与以下策略类似的策略，其中包含适用于 OpenSearch 服务域的相应操作和资源。请注意，**委托**人将是 AWS AppSync 数据源角色，如果您让上述控制台创建该角色，则可以在 IAM 控制台中找到该角色。

## 连接解析器
<a name="connecting-a-resolver-js"></a>

现在，数据源已连接到您的 OpenSearch 服务域，您可以使用解析器将其连接到您的 GraphQL 架构，如以下示例所示：

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

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

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

请注意，有一个用户定义的 `Post` 类型（具有一个 `id` 字段）。在以下示例中，我们假设有一个过程（可以自动化）将此类型放入您的 OpenSearch 服务域，该过程将映射到索引`/post/_doc``post`所在位置的路径根。从该根路径中，您可以执行单独文档搜索、使用 `/id/post*` 的通配符搜索或使用 `/post/_search` 路径的多文档搜索。例如，如果您具有另一个名为 `User` 的类型，您可以使用名为 `user` 的新索引对文档编制索引，然后使用 `/user/_search` **路径**执行搜索。

在 AWS AppSync 控制台的**架构**编辑器中，修改前面的`Posts`架构以包含`searchPosts`查询：

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

保存架构。在**解析器**窗格中，找到 `searchPosts` 并选择**附加**。选择您的 OpenSearch 服务数据源并保存解析器。使用下面的代码片段更新解析器的代码：

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

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

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

这假设前面的架构中包含已在 S OpenSearch ervice 中为该`post`字段编制索引的文档。如果您以不同方式设置数据结构，则需要相应地进行更新。

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

前面的解析器请求处理程序为所有记录执行简单的查询。假设您想要按某个特定作者进行搜索。此外，假设您希望将该作者指定为 GraphQL 查询中定义的参数。在 AWS AppSync 控制台的**架构**编辑器中，添加一个`allPostsByAuthor`查询：

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

在**解析器**窗格中，找到 `allPostsByAuthor` 并选择**附加**。选择 OpenSearch 服务数据源并使用以下代码：

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

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

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

请注意，`body` 用一个针对 `author` 字段的术语查询填充，它将作为一个参数从客户端进行传递。或者，您可以选择使用预填充的信息，例如标准文本。

## 向 OpenSearch 服务添加数据
<a name="adding-data-to-es-js"></a>

由于 GraphQL 突变，您可能需要向 OpenSearch 服务域中添加数据。这是一个用于搜索和其他用途的强大机制。由于您可以使用 GraphQL 订阅来[实现实时数据](aws-appsync-real-time-data.md)，因此它可以用作通知客户服务域中数据 OpenSearch 更新的机制。

返回 AWS AppSync 控制台中的 “**架构**” 页面，然后为`addPost()`突变选择**附加**。再次选择 OpenSearch 服务数据源并使用以下代码：

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

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

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

与之前一样，这是一个说明如何设置数据结构的示例。如果您具有不同的字段名称或索引，则需要更新 `path` 和 `body`。该示例还说明了如何在请求处理程序中使用 `context.arguments`（也可以写成 `ctx.args`）。

## 检索单个文档
<a name="retrieving-a-single-document-js"></a>

最后，如果要使用架构中的`getPost(id:ID)`查询来返回单个文档，请在 AWS AppSync 控制台的**架构**编辑器中找到此查询，然后选择 Att **ach**。再次选择 OpenSearch 服务数据源并使用以下代码：

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

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

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

## 执行查询和变更
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations-js"></a>

现在，您应该能够对您的 OpenSearch 服务域执行 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 服务域运行`searchPosts`查询：

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

## 最佳实践
<a name="best-practices-js"></a>
+ OpenSearch 服务应该用于查询数据，而不是作为您的主数据库。如组合 GraphQL 解 OpenSearch 析器中所述，您可能需要将服务与亚马逊 DynamoDB [结合](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-combining-graphql-resolvers-js.html)使用。
+ 仅通过允许 AWS AppSync 服务角色访问集群来授予对域的访问权限。
+ 您可以通过最低成本的集群先开始小规模开发，然后随着您转向生产阶段，而转至具有高可用性 (HA) 的较大集群。

# 在中执行 DynamoDB 事务 AWS AppSync
<a name="tutorial-dynamodb-transact-js"></a>

AWS AppSync 支持对单个区域中的一个或多个表使用 Amazon DynamoDB 事务操作。支持的操作为 `TransactGetItems` 和 `TransactWriteItems`。通过在中使用这些功能 AWS AppSync，您可以执行以下任务：
+ 在单个查询中传递键列表，并从表中返回结果
+ 在单个查询中从一个或多个表中读取记录
+ 以 all-or-nothing某种方式将事务中的记录写入一个或多个表
+ 在满足某些条件时运行事务

## Permissions
<a name="permissions-js"></a>

与其他解析器一样，您需要在中创建数据源， 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/*"
            ]
        }
    ]
}
```

------

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

## 数据来源
<a name="data-source-js"></a>

为简便起见，我们将为本教程中使用的所有解析器使用同一个数据来源。

我们具有两个名为 **savingAccounts** 和 **checkingAccounts** 的表，它们将 `accountNumber` 作为分区键，还有一个 **transactionHistory** 表，它将 `transactionId` 作为分区键。您可以使用下面的 CLI 命令创建表。确保将 `region` 替换为您的区域。

**使用 CLI**

```
aws dynamodb create-table --table-name savingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name checkingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name transactionHistory \
  --attribute-definitions AttributeName=transactionId,AttributeType=S \
  --key-schema AttributeName=transactionId,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region
```

在 AWS AppSync 控制台**的数据源**中，创建一个新的 DynamoDB 数据源并将其命名。**TransactTutorial**选择 **savingAccounts** 以作为表（尽管在使用事务时具体的表并不重要）。选择创建新角色和数据来源。您可以检查数据来源配置以查看生成的角色的名称。在 IAM 控制台中，您可以添加一个允许数据来源与所有表交互的内联策略。

将 `region` 和 `accountID` 替换为您的区域和账户 ID：

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

****  

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

------

## 事务
<a name="transactions-js"></a>

对于本示例，上下文是一个典型的银行交易，我们将使用 `TransactWriteItems` 来执行以下操作：
+ 从储蓄账户转账到支票账户
+ 为每个交易生成新的交易记录

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

**警告**  
与冲突检测和解决功能一起使用时不支持 `TransactWriteItems`。必须禁用这些设置以防止可能出现的错误。

我们按如下所示定义我们的 GraphQL 架构：

```
type SavingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type CheckingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type TransactionHistory {
    transactionId: ID!
    from: String
    to: String
    amount: Float
}

type TransactionResult {
    savingAccounts: [SavingAccount]
    checkingAccounts: [CheckingAccount]
    transactionHistory: [TransactionHistory]
}

input SavingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input CheckingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input TransactionInput {
    savingAccountNumber: String!
    checkingAccountNumber: String!
    amount: Float!
}

type Query {
    getAccounts(savingAccountNumbers: [String], checkingAccountNumbers: [String]): TransactionResult
}

type Mutation {
    populateAccounts(savingAccounts: [SavingAccountInput], checkingAccounts: [CheckingAccountInput]): TransactionResult
    transferMoney(transactions: [TransactionInput]): TransactionResult
}
```

### TransactWriteItems -填充账户
<a name="transactwriteitems-populate-accounts-js"></a>

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

在“架构”部分中，单击 `Mutation.populateAccounts` 操作旁边的**附加**。选择 `TransactTutorial` 数据来源，然后选择**创建**。

现在使用以下代码：

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

export function request(ctx) {
	const { savingAccounts, checkingAccounts } = ctx.args

	const savings = savingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'savingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})

	const checkings = checkingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'checkingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})
	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const { savingAccounts: sInput, checkingAccounts: cInput } = ctx.args
	const keys = ctx.result.keys
	const savingAccounts = sInput.map((_, i) => keys[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => keys[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

保存解析器并导航到 AWS AppSync 控制台的 “**查询**” 部分以填充账户。

执行以下变更：

```
mutation populateAccounts {
  populateAccounts (
    savingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 100},
      {accountNumber: "2", username: "Amy", balance: 90},
      {accountNumber: "3", username: "Lily", balance: 80},
    ]
    checkingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 70},
      {accountNumber: "2", username: "Amy", balance: 60},
      {accountNumber: "3", username: "Lily", balance: 50},
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
  }
}
```

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

使用 DynamoDB 控制台验证是否在 **savingAccounts** 和 **checkingAccounts** 表中显示数据。

### TransactWriteItems -汇款
<a name="transactwriteitems-transfer-money-js"></a>

使用以下代码将一个解析器附加到 `transferMoney` 变更。对于每笔转账，我们都需要支票账户和储蓄账户具有成功修饰符，并且需要跟踪交易中的转账。

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

export function request(ctx) {
	const transactions = ctx.args.transactions

	const savings = []
	const checkings = []
	const history = []
	transactions.forEach((t) => {
		const { savingAccountNumber, checkingAccountNumber, amount } = t
		savings.push({
			table: 'savingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: savingAccountNumber }),
			update: {
				expression: 'SET balance = balance - :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		checkings.push({
			table: 'checkingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: checkingAccountNumber }),
			update: {
				expression: 'SET balance = balance + :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		history.push({
			table: 'transactionHistory',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ transactionId: util.autoId() }),
			attributeValues: util.dynamodb.toMapValues({
				from: savingAccountNumber,
				to: checkingAccountNumber,
				amount,
			}),
		})
	})

	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings, ...history],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const tInput = ctx.args.transactions
	const tLength = tInput.length
	const keys = ctx.result.keys
	const savingAccounts = tInput.map((_, i) => keys[tLength * 0 + i])
	const checkingAccounts = tInput.map((_, i) => keys[tLength * 1 + i])
	const transactionHistory = tInput.map((_, i) => keys[tLength * 2 + i])
	return { savingAccounts, checkingAccounts, transactionHistory }
}
```

现在，导航到 AWS AppSync 控制台的 “**查询**” 部分并按如下方式执行 **TransferMoney** 突变：

```
mutation write {
  transferMoney(
    transactions: [
      {savingAccountNumber: "1", checkingAccountNumber: "1", amount: 7.5},
      {savingAccountNumber: "2", checkingAccountNumber: "2", amount: 6.0},
      {savingAccountNumber: "3", checkingAccountNumber: "3", amount: 3.3}
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
    transactionHistory {
      transactionId
    }
  }
}
```

我们在一个变更中发送了三笔银行交易。使用 DynamoDB 控制台验证是否在 **savingAccounts**、**checkingAccounts** 和 **transactionHistory** 表中显示数据。

### TransactGetItems -检索账户
<a name="transactgetitems-retrieve-accounts-js"></a>

为了在单个事务请求中从储蓄和支票账户中检索详细信息，我们将一个解析器附加到架构上的 `Query.getAccounts` GraphQL 操作。选择**附加**，选择在教程开始时创建的相同 `TransactTutorial` 数据来源。使用以下代码：

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

export function request(ctx) {
	const { savingAccountNumbers, checkingAccountNumbers } = ctx.args

	const savings = savingAccountNumbers.map((accountNumber) => {
		return { table: 'savingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	const checkings = checkingAccountNumbers.map((accountNumber) => {
		return { table: 'checkingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	return {
		version: '2018-05-29',
		operation: 'TransactGetItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}

	const { savingAccountNumbers: sInput, checkingAccountNumbers: cInput } = ctx.args
	const items = ctx.result.items
	const savingAccounts = sInput.map((_, i) => items[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => items[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

保存解析器并导航到 AWS AppSync 控制台的 “**查询**” 部分。为了检索储蓄和支票账户，执行以下查询：

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

我们已经成功演示了使用DynamoDB事务的用法。 AWS AppSync

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

AWS AppSync 支持对单个区域中的一个或多个表使用 Amazon DynamoDB 批量操作。支持的操作为 `BatchGetItem`、`BatchPutItem` 和 `BatchDeleteItem`。通过在中使用这些功能 AWS AppSync，您可以执行以下任务：
+ 在单个查询中传递键列表，并从表中返回结果
+ 在单个查询中从一个或多个表中读取记录
+ 将记录批量写入到一个或多个表
+ 在可能存在关系的多个表中有条件地写入或删除记录

中的批处理操作与非批处理操作 AWS AppSync 有两个主要区别：
+ 数据来源角色必须具有解析器访问的所有表的权限。
+ 解析器的表规范是请求对象的一部分。

## 单个表批处理
<a name="single-table-batch-js"></a>

**警告**  
与冲突检测和解决功能一起使用时不支持 `BatchPutItem` 和 `BatchDeleteItem`。必须禁用这些设置以防止可能出现的错误。

首先，我们创建一个新的 GraphQL API。在 AWS AppSync 控制台中，选择 “**创建 API**”、“**GraphQL APIs**” 和 “**从头开始设计**”。将您的 API 命名为 `BatchTutorial API`，选择**下一步**，在**指定 GraphQL 资源**步骤中选择**稍后创建 GraphQL 资源**，然后单击**下一步**。检查您的详细信息并创建 API。转到 “**架构**” 页面并粘贴以下架构，请注意，对于查询，我们将传入一个列表 IDs：

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

input PostInput {
    id: ID!
    title: String
}

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

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

保存您的架构，并选择页面顶部的**创建资源**。选择**使用现有的类型**，并选择 `Post` 类型。将您的表命名为 `Posts`。确保**主键**设置为 `id`，取消选择**自动生成 GraphQL**（您将提供自己的代码），然后选择**创建**。首先， AWS AppSync 创建一个新的 DynamoDB 表以及一个使用相应角色连接到该表的数据来源。不过，您仍然需要为该角色添加一些权限。转到**数据来源页**面，并选择新的数据来源。在**选择现有角色**下面，您会注意到已为该表自动创建了一个角色。记下该角色（应该看起来像`appsync-ds-ddb-aaabbbcccddd-Posts`），然后转到 IAM 控制台 ([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/))。在 IAM 控制台中，选择**角色**，然后从该表中选择您的角色。在您的角色中，在**权限策略**下面，单击策略旁边的“`+`”（名称应与角色名称相似）。在显示该策略时，选择折叠菜单顶部的**编辑**。您需要为您的策略添加批处理权限，具体来说是 `dynamodb:BatchGetItem` 和 `dynamodb:BatchWriteItem`。代码片段如下所示：

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

****  

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

------

选择**下一步**，然后选择**保存更改**。您的策略现在应该允许进行批处理。

返回 AWS AppSync 控制台，转到 “**架构**” 页面，然后选择`Mutation.batchAdd`字段旁边的 “**附加**”。将 `Posts` 表作为数据来源以创建解析器。在代码编辑器中，将处理程序替换为下面的代码片段。该代码片段自动获取 GraphQL `input PostInput` 类型中的每个项目，并构建 `BatchPutItem` 操作所需的映射：

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

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

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

导航到 AWS AppSync 控制台的 “**查询**” 页面并运行以下`batchAdd`变更：

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

您应该会看到在屏幕上输出的结果；可以在 DynamoDB 控制台中扫描写入到 `Posts` 表的值以验证这一点。

接下来，重复附加解析器的过程，但对于 `Query.batchGet` 字段，将 `Posts` 表作为数据来源。将处理程序替换为以下代码。这会自动接受 GraphQL `ids:[]` 类型的每个项目并构建 `BatchGetItem` 操作所需的一个映射：

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

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

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

现在，返回 AWS AppSync 控制台的 “**查询**” 页面并运行以下`batchGet`查询：

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

这应返回您早前添加的两个 `id` 值的结果。请注意，对于值为 `3` 的 `id`，将返回 `null` 值。这是因为您的 `Posts` 表中还没有具有该值的记录。另请注意， AWS AppSync 返回结果的顺序与传递给查询的密钥的顺序相同，这是一项代表您 AWS AppSync 执行的附加功能。因此，如果切换到 `batchGet(ids:[1,3,2])`，您会看到顺序发生了变化。您还将了解哪个 `id` 返回了 `null` 值。

最后，将另一个解析器附加到 `Mutation.batchDelete` 字段，并将 `Posts` 表作为数据来源。将处理程序替换为以下代码。这会自动接受 GraphQL `ids:[]` 类型的每个项目并构建 `BatchGetItem` 操作所需的一个映射：

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

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

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

现在，返回 AWS AppSync 控制台的 **Q** ueries 页面并运行以下`batchDelete`变更：

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

现在应删除 `id` 为 `1` 和 `2` 的记录。如果您更早重新运行 `batchGet()` 查询，则应返回 `null`。

## 多个表批处理
<a name="multi-table-batch-js"></a>

**警告**  
与冲突检测和解决功能一起使用时不支持 `BatchPutItem` 和 `BatchDeleteItem`。必须禁用这些设置以防止可能出现的错误。

AWS AppSync 还允许您对表执行批量操作。我们来构建更复杂的应用程序。想象一下，我们构建一个宠物健康应用程序，其中传感器报告宠物的位置和体温。传感器由电池供电，并每隔几分钟尝试连接到网络。当传感器建立连接时，它会将其读数发送到我们 AWS AppSync 的 API。然后，触发器分析数据，这样，就可以向宠物主人显示控制面板。我们重点关注如何表示传感器与后端数据存储之间的交互。

在 AWS AppSync 控制台中，选择 “**创建 API**”、“**GraphQL APIs**” 和 “**从头开始设计**”。将您的 API 命名为 `MultiBatchTutorial API`，选择**下一步**，在**指定 GraphQL 资源**步骤中选择**稍后创建 GraphQL 资源**，然后单击**下一步**。检查您的详细信息并创建 API。转到**架构**页面，并粘贴和保存以下架构：

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

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

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

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

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

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

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

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

我们需要创建两个 DynamoDB 表：
+ `locationReadings` 存储传感器位置读数。
+ `temperatureReadings` 存储传感器温度读数。

这两个表具有相同的主键结构：将 `sensorId (String)` 作为分区键，并将 `timestamp (String)` 作为排序键。

选择页面顶部的**创建资源**。选择**使用现有的类型**，并选择 `locationReadings` 类型。将您的表命名为 `locationReadings`。确保将**主键**设置为 `sensorId`，并将排序键设置为 `timestamp`。取消选择**自动生成 GraphQL**（您将提供自己的代码），然后选择**创建**。为 `temperatureReadings` 重复该过程，并将 `temperatureReadings` 作为类型和表名称。使用与上面相同的键。

您的新表将包含自动生成的角色。您仍然需要为这些角色添加一些权限。转到**数据来源**页面并选择 `locationReadings`。在**选择现有角色**下面，您可以看到该角色。记下该角色（应该看起来像`appsync-ds-ddb-aaabbbcccddd-locationReadings`），然后转到 IAM 控制台 ([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/))。在 IAM 控制台中，选择**角色**，然后从该表中选择您的角色。在您的角色中，在**权限策略**下面，单击策略旁边的“`+`”（名称应与角色名称相似）。在显示该策略时，选择折叠菜单顶部的**编辑**。您需要为该策略添加权限。代码片段如下所示：

选择**下一步**，然后选择**保存更改**。使用上面相同的策略片段，为 `temperatureReadings` 数据来源重复该过程。

### BatchPutItem -记录传感器读数
<a name="batchputitem-recording-sensor-readings-js"></a>

我们的传感器需要能够在连接到 Internet 后立即发送其读数。GraphQL 字段 `Mutation.recordReadings` 是传感器将用来执行上述操作的 API。我们需要在该字段中添加一个解析器。

在 AWS AppSync 控制台的 “**架构**” 页面中，选择`Mutation.recordReadings`字段旁边的 “**附加**”。在下一个屏幕上，将 `locationReadings` 表作为数据来源以创建解析器。

在创建解析器后，在编辑器中将处理程序替换为以下代码。我们可以通过 `BatchPutItem` 操作指定多个表：

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

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

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

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

使用批处理操作，可能会从调用中同时返回错误和结果。在这种情况下，我们可以自主执行一些额外的错误处理。

**注意**  
使用 `utils.appendError()` 与 `util.error()` 类似，主要区别在于，它不会中断请求或响应处理程序评估。相反，它指示字段存在错误，但允许评估处理程序，从而将数据返回到调用方。在您的应用程序需要返回部分结果时，我们建议您使用 `utils.appendError()`。

保存解析器并导航到 AWS AppSync 控制台中的 “**查询**” 页面。我们现在可以发送一些传感器读数。

执行以下变更：

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

我们在一个变更中发送了 10 个传感器读数，这些读数拆分到两个表中。使用 DynamoDB 控制台验证是否在 `locationReadings` 和 `temperatureReadings` 表中显示数据。

### BatchDeleteItem -删除传感器读数
<a name="batchdeleteitem-deleting-sensor-readings-js"></a>

同样，我们还需要能够批量删除传感器读数。我们使用 `Mutation.deleteReadings` GraphQL 字段来实现此目的。在 AWS AppSync 控制台的 “**架构**” 页面中，选择`Mutation.deleteReadings`字段旁边的 “**附加**”。在下一个屏幕上，将 `locationReadings` 表作为数据来源以创建解析器。

在创建解析器后，在代码编辑器中将处理程序替换为下面的代码片段。在该解析器中，我们使用帮助程序函数映射器，该映射器从提供的输入中提取 `sensorId` 和 `timestamp`。

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

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

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

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

保存解析器并导航到 AWS AppSync 控制台中的 “**查询**” 页面。现在，让我们删除几个传感器读数。

执行以下变更：

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

**注意**  
与 `DeleteItem` 操作相反，响应中不会返回完全删除的项目。只返回传递的键。要了解更多信息，请参阅 Dynamo [D JavaScript B BatchDeleteItem 的解析器内函数参考。](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item)

通过 DynamoDB 控制台验证是否从 `locationReadings` 和 `temperatureReadings` 表中删除了这两个读数。

### BatchGetItem -检索读数
<a name="batchgetitem-retrieve-readings-js"></a>

我们的应用程序的另一个常见操作是，检索传感器在特定时间点的读数。我们将解析器附加到架构上的 `Query.getReadings` GraphQL 字段。在 AWS AppSync 控制台的 “**架构**” 页面中，选择`Query.getReadings`字段旁边的 “**附加**”。在下一个屏幕上，将 `locationReadings` 表作为数据来源以创建解析器。

让我们使用以下代码：

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

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

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

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

保存解析器并导航到 AWS AppSync 控制台中的 “**查询**” 页面。现在，让我们检索传感器读数。

执行以下查询：

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

我们已经成功演示了使用的 DynamoDB 批处理操作的用法。 AWS AppSync

## 错误处理
<a name="error-handling-js"></a>

在中 AWS AppSync，数据源操作有时会返回部分结果。部分结果是一个术语，我们用它来表示操作的输出中包含某些数据和一个错误。由于错误处理本质上是特定于应用程序的， AWS AppSync 因此您可以有机会在响应处理程序中处理错误。上下文中的解析器调用错误（如果有）为 `ctx.error`。调用错误始终包含一条消息和一个类型，可作为属性 `ctx.error.message` 和 `ctx.error.type` 进行访问。在响应处理程序中，您可以使用三种方法处理部分结果：

1. 仅返回数据以忽略调用错误。

1. 停止处理程序评估以引发错误（使用 `util.error(...)`），这不会返回任何数据。

1. 附加一个错误（使用 `util.appendError(...)`）并且也返回数据。

让我们通过 DynamoDB 批处理操作分别说明上述三点。

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

借助 DynamoDB 批处理操作，批处理可能会部分完成。也就是说，某些请求的项目或键未得到处理。如果无法完成批处理， AWS AppSync 则将在上下文中设置未处理的项目和调用错误。

我们将使用本教程前一部分中 `Query.getReadings` 操作的 `BatchGetItem` 字段配置来实施错误处理。这一次，我们假定在执行 `Query.getReadings` 字段时，`temperatureReadings` DynamoDB 表耗尽了预置的吞吐量。DynamoDB 在第二次尝试 AWS AppSync 时引发了 `ProvisionedThroughputExceededException` a，用于处理批次中的剩余元素。

以下 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": []
}
```

关于上下文需要注意的几点：
+ 调用错误已在 b `ctx.error` y 的上下文中设置 AWS AppSync，错误类型已设置为`DynamoDB:ProvisionedThroughputExceededException`。
+ 即使存在错误，也会在 `ctx.result.data` 中为每个表映射结果。
+ 在 `ctx.result.data.unprocessedKeys` 中提供了未处理的键。在这里， AWS AppSync 由于表吞吐量不足，无法使用密钥检索项目（sensorid:1，timestamp: 2018-02-01T 17:21:05.000 \$1 08:00）。

**注意**  
对于 `BatchPutItem`，它是 `ctx.result.data.unprocessedItems`。对于 `BatchDeleteItem`，它是 `ctx.result.data.unprocessedKeys`。

我们通过三种不同方式处理此错误。

#### 1. 承受调用错误
<a name="swallowing-the-invocation-error-js"></a>

返回数据而不处理调用错误：这会有效地承受此错误，同时使给定 GraphQL 字段的结果始终成功。

我们编写的代码是熟悉的，并且仅关注结果数据。

**响应处理程序**

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

**GraphQL 响应**

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

将不向错误响应中添加任何错误，因为只对数据执行了操作。

#### 2. 引发错误以中止执行响应处理程序
<a name="raising-an-error-to-abort-the-response-execution-js"></a>

从客户端角度看，在应将部分失败视为完全失败时，您可以中止执行响应处理程序以防止返回数据。`util.error(...)` 实用程序方法实现完全此行为。

**响应处理程序代码**

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

**GraphQL 响应**

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

即使可能已从 DynamoDB 批处理操作返回了一些结果，我们也选择引发错误，这样，`getReadings` GraphQL 字段为 Null，并且此错误已添加到 GraphQL 响应的*错误*数据块中。

#### 3. 追加错误以返回数据和错误
<a name="appending-an-error-to-return-both-data-and-errors-js"></a>

在某些情况下，为了提供更好的用户体验，应用程序可以返回部分结果并向其客户端通知未处理的项目。客户端可以决定是实施重试，还是将错误翻译出来并返回给最终用户。`util.appendError(...)` 是一种实现该行为的实用程序方法，它让应用程序设计者在上下文中附加错误，而不干扰响应处理程序评估。评估响应处理程序后， AWS AppSync 将通过将任何上下文错误附加到 GraphQL 响应的错误块来处理它们。

**响应处理程序代码**

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

我们在 GraphQL 响应的错误块中转发了调用错误和 `unprocessedKeys` 元素。`getReadings` 字段也从 `locationReadings` 表中返回部分数据，如下面的响应中所示。

**GraphQL 响应**

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

# 在中使用 HTTP 解析器 AWS AppSync
<a name="tutorial-http-resolvers-js"></a>

AWS AppSync 允许您使用支持的数据源（即亚马逊 DynamoDB AWS Lambda、 OpenSearch 亚马逊服务或 Amazon Aurora）执行各种操作，此外还可以使用任何任意 HTTP 终端节点来解析 GraphQL 字段。在您的 HTTP 终端节点可用后，您可以使用数据来源连接它们。然后，您可以在架构中配置一个解析器以执行 GraphQL 操作（如查询、变更和订阅）。本教程将引导您了解一些常见示例。

在本教程中，您将使用 REST API（使用 Amazon API Gateway 和 Lambda 创建）和 GraphQL AWS AppSync 终端节点。

## 创建 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>

要在以下位置创建 GraphQL API，请执行以下操作： AWS AppSync

1. 打开 AWS AppSync 控制台并选择**创建 API**。

1. 选择 **GraphQL APIs**，然后选择 “**从头开始设计**”。选择**下一步**。

1. 对于 API 名称，请键入 `UserData`。选择**下一步**。

1. 选择 `Create GraphQL resources later`。选择**下一步**。

1. 检查您的输入，然后选择**创建 API**。

 AWS AppSync 控制台使用 API 密钥身份验证模式为您创建一个新的 GraphQL API。您可以使用控制台进一步配置 GraphQL API 并运行请求。

## 创建 GraphQL 架构
<a name="creating-a-graphql-schema"></a>

现在，您有一个 GraphQL API，让我们创建一个 GraphQL 架构。在 AWS AppSync 控制台的**架构**编辑器中，使用以下片段：

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

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

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

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

## 配置您的 HTTP 数据来源
<a name="configure-your-http-data-source"></a>

要配置 HTTP 数据来源，请执行以下操作：

1. 在 AWS AppSync GraphQL API **的数据源**页面中，选择**创建数据**源。

1. 输入数据来源的名称，例如 `HTTP_Example`。

1. 在**数据来源类型**中，选择 **HTTP 终端节点**。

1. 将终端节点设置为教程开始时创建的 API Gateway 终端节点。如果您导航到 Lambda 控制台并在**应用程序**下面找到您的应用程序，则可以找到堆栈生成的终端节点。在应用程序的设置中，您应该会看到一个 **API 终端节点**，它是您在 AWS AppSync中的终端节点。确保不要将阶段名称作为终端节点的一部分包含在内。例如，如果您的终端节点是 `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com/v1`，您将键入 `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com`。

**注意**  
目前，仅支持公共终端节点 AWS AppSync。  
有关该 AWS AppSync 服务可识别的认证机构的更多信息，请参阅 [HTTPS 端点的认证机构 (CA)](http-cert-authorities.md#aws-appsync-http-certificate-authorities)。 AWS AppSync 

## 配置解析器
<a name="configuring-resolvers"></a>

在该步骤中，您将 HTTP 数据来源连接到 `getUser` 和 `addUser` 查询。

要设置 `getUser` 解析器，请执行以下操作：

1. 在您的 AWS AppSync GraphQL API 中，选择**架构**选项卡。

1. 在**架构**编辑器右侧的**解析器**窗格中的 **Query** 类型下面，找到 `getUser` 字段并选择**附加**。

1. 将解析器类型保留为 `Unit`，并将运行时保留为 `APPSYNC_JS`。

1. 在**数据来源名称**中，选择您以前创建的 HTTP 终端节点。

1. 选择**创建**。

1. 在**解析器**代码编辑器中，添加以下代码片段以作为您的请求处理程序：

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

1. 添加以下代码片段以作为您的响应处理程序：

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

1. 选择 **Query (查询)** 选项卡，然后运行以下查询：

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

   此查询应返回以下响应：

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

要设置 `addUser` 解析器，请执行以下操作：

1. 选择**架构**选项卡。

1. 在**架构**编辑器右侧的**解析器**窗格中的 **Query** 类型下面，找到 `addUser` 字段并选择**附加**。

1. 将解析器类型保留为 `Unit`，并将运行时保留为 `APPSYNC_JS`。

1. 在**数据来源名称**中，选择您以前创建的 HTTP 终端节点。

1. 选择**创建**。

1. 在**解析器**代码编辑器中，添加以下代码片段以作为您的请求处理程序：

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

1. 添加以下代码片段以作为您的响应处理程序：

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

1. 选择 **Query (查询)** 选项卡，然后运行以下查询：

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

   如果再次运行 `getUser` 查询，它应该会返回以下响应：

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

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

您可以使用 HTTP 解析器为服务设置 GraphQL API 接口 AWS 。对的 HTTP 请求 AWS 必须使用[签名版本 4 流程](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)进行签名，这样 AWS 才能识别谁发送了这些请求。 AWS AppSync 当您将 IAM 角色与 HTTP 数据源关联时，代表您计算签名。

您还提供了两个额外的组件来使用 HTTP 解析器调用 AWS 服务：
+ 有权调用 AWS 服务的 IAM 角色 APIs
+ 数据来源中的签名配置

例如，如果您想使用 HTTP 解析器调用该[ListGraphqlApis 操作](https://docs.aws.amazon.com/appsync/latest/APIReference/API_ListGraphqlApis.html)，请先[创建一个附加以下策略的 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 数据源 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>
```

当您将解析器附加到架构中的字段时，请使用以下请求映射模板进行调用 AWS AppSync：

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

当您对该数据源运行 GraphQL 查询时，使用您提供的角色对请求进行 AWS AppSync 签名，并在请求中包含签名。该查询会返回您在该区域的账户 APIs 中的 AWS AppSync GraphQL 列表。 AWS 

# 在中使用带有数据 API 的 Aurora PostgreSQL AWS AppSync
<a name="aurora-serverless-tutorial-js"></a>

 

了解如何使用将你的 GraphQL API 连接到 Aurora PostgreSQL 数据库。 AWS AppSync这种集成使您能够通过 GraphQL 操作执行 SQL 查询和突变，从而构建可扩展、数据驱动的应用程序。 AWS AppSync 提供了一个数据源，用于对启用了数据 API 的 Amazon Aurora 集群执行 SQL 语句。您可以使用 AWS AppSync 解析器通过 GraphQL 查询、突变和订阅对数据 API 运行 SQL 语句。

在开始本教程之前，您应该对 AWS 服务和 GraphQL 概念有基本的了解。

**注意**  
本教程使用 `US-EAST-1` 区域。

**Topics**
+ [设置您的 Aurora PostgreSQL 数据库](#creating-clusters)
+ [创建数据库和表](#creating-db-table)
+ [创建 GraphQL 架构](#rds-graphql-schema)
+ [RDS 的解析器](#rds-resolvers)
+ [删除集群](#rds-delete-cluster)

## 设置您的 Aurora PostgreSQL 数据库
<a name="creating-clusters"></a>

在向添加 Amazon RDS 数据源之前 AWS AppSync，请执行以下操作。

1. 在 Aurora Serverless v2 集群上启用数据 API。

1. 使用配置密钥 AWS Secrets Manager

1. 使用以下 AWS CLI 命令创建集群。

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

这将为集群返回一个 ARN。创建集群后，必须使用以下 AWS CLI 命令添加一个 Serverless v2 实例。

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

**注意**  
这些端点需要一段时间才会活跃。您可以在 RDS 控制台中相应集群的**连接和安全**选项卡中查看其状态。

使用以下 AWS CLI 命令检查集群状态。

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

通过 AWS Secrets Manager 控制台创建密钥， AWS CLI 或者使用上一步中的`USERNAME`和输入文件创建密`COMPLEX_PASSWORD`钥，如下所示：

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

将其作为参数传递给 AWS CLI：

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

这将为密钥返回 ARN。在控制台中创建数据源时，**请记**下 Aurora Serverless v2 集群的 ARN 和密钥，以备日后使用。 AWS AppSync 

## 创建数据库和表
<a name="creating-db-table"></a>

首先，创建一个名为 `TESTDB` 的数据库。在 PostgreSQL 中，数据库是存放表和其他 SQL 对象的容器。确认在将 Aurora Serverless v2 集群添加到 AWS AppSync API 之前已进行正确配置。首先，使用 `--sql` 参数创建 *TESTDB* 数据库，如下所示。

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

 如果运行而未出现错误，请使用 `create table` 命令添加两个表：

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

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

如果成功，则在 API 中将集群添加为数据来源。

## 创建 GraphQL 架构
<a name="rds-graphql-schema"></a>

现在，您的 Aurora Serverless v2 数据 API 正在使用已配置的表运行，我们将创建一个 GraphQL 架构。您可以通过使用 API 创建向导从现有数据库导入表配置，从而快速创建 API。

首先：

1. 在 AWS AppSync 控制台中，选择**创建 API**，然后选择从 **Amazon Aurora 集群开始**。

1. 指定 API 详细信息，例如 **API 名称**，然后选择要生成 API 的数据库。

1. 选择数据库。如果需要，请更新区域，然后选择您的 Aurora 集群和 *TESTDB* 数据库。

1. 选择您的密钥，然后选择**导入**。

1. 发现表后，更新类型名称。将 `Todos` 更改为 `Todo`，并将 `Tasks` 更改为 `Task`。

1. 通过选择**预览架构**来预览生成的架构。您的架构将如下所示：

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

1. 对于该角色，您可以 AWS AppSync 创建一个新角色，也可以使用类似于以下策略的策略创建一个角色：

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

****  

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

------

   请注意，此策略中有两条您要授予角色访问权限的语句。第一个资源是您的 Aurora 集群，第二个资源是您的 AWS Secrets Manager ARN。

   选择**下一步**，查看配置详细信息，然后选择**创建 API**。您现在已拥有完全正常运行的 API。您可以在**架构**页面上查看 API 的完整详细信息。

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

API 创建流程会自动创建解析器来与我们的类型进行交互。**架构**页面中包含以下一些解析器。
+ 通过 `Mutation.createTodo` 字段创建 `todo`。
+ 通过 `Mutation.updateTodo` 字段更新 `todo`。
+ 通过 `Mutation.deleteTodo` 字段删除 `todo`。
+ 通过 `Query.getTodo` 字段获取单个 `todo`。
+ 通过 `Query.listTodos` 字段列出所有 `todos`。

您会发现该 `Task` 类型附带了类似的字段和解析器。让我们更仔细地看看一些解析器。

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

在 AWS AppSync 控制台的架构编辑器中，选择右`testdb`侧的`createTodo(...): Todo`。解析器代码使用 `rds` 模块中的 `insert` 函数动态创建向 `todos` 表中添加数据的插入语句。因为我们正在使用 Postgres，所以我们可以利用 `returning` 语句来取回插入的数据。

更新以下解析器以正确指定 `due` 字段的 `DATE` 类型。

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

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

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

保存解析器。类型提示将输入对象中的 `due` 正确标记为 `DATE` 类型。这允许 Postgres 引擎正确解释该值。接下来，更新您的架构以从 `CreateTodo` 输入中删除 `id`。因为我们的 Postgres 数据库可以返回生成的 ID，所以您可以依靠它来创建并以单个请求的形式返回结果，如下所示。

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

进行更改并更新您的架构。前往**查询**编辑器，以向数据库添加一个项目，如下所示。

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

您将获得以下结果。

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

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

在控制台的架构编辑器中，在右侧选择 `listTodos(id: ID!): Todo` 旁边的 `testdb`。请求处理程序使用 select 实用程序函数在运行时动态生成请求。

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

我们想根据 `due` 日期筛选 `todos`。让我们更新解析器以将 `due` 值强制转换为 `DATE`。更新导入列表和请求处理程序，如下所示。

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

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

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

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

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

在**查询**编辑器中，执行下列操作。

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

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

您也可以对 `Todo` 执行 `update`。从**查询**编辑器中，我们来更新第一个 `Todo` 项目（`id` 为 `1`）。

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

请注意，您必须指定要更新的项目的 `id`。您也可以指定一个条件，以仅更新符合特定条件的项目。例如，如果描述以 `edits` 开头，我们可能只想编辑该项目，如下所示。

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

就像我们处理 `create` 和 `list` 操作的方式一样，我们可以更新解析器以将 `due` 字段强制转换为 `DATE`。将这些更改保存到 `updateTodo`，如下所示。

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

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

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

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

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

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

现在尝试使用一个条件进行更新：

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

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

您可以通过 `deleteTodo` 突变对 `Todo` 执行 `delete`。这就像 `updateTodo` 变更一样，您必须指定要删除的项目的 `id`，如下所示。

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

### 编写自定义查询
<a name="writing-custom-queries"></a>

我们已经使用 `rds` 模块实用程序来创建我们的 SQL 语句。我们还可以编写自己的自定义静态语句来与我们的数据库进行交互。首先，更新架构以从 `CreateTask` 输入中删除 `id` 字段。

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

接下来，创建几个任务。任务与 `Todo` 有外键关系，如下所示。

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

在您的 `Query` 类型中创建一个名为 `getTodoAndTasks` 的新字段，如下所示。

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

向 `Todo` 类型添加一个 `tasks` 字段，如下所示。

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

保存架构。从控制台的架构编辑器中，在右侧为 `getTodosAndTasks(id: Int!): Todo` 选择**附加解析器**。选择您的 Amazon RDS 数据来源。使用以下代码更新您的解析器。

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

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

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

在此代码中，我们使用 `sql` 标签模板编写了一条 SQL 语句，我们可以在运行时安全地将动态值传递给该语句。`createPgStatement` 一次最多可以处理两个 SQL 请求。我们用它来为 `todo` 发送一个查询，并为 `tasks` 发送另一个查询。您可以使用 `JOIN` 语句或任何其它方法来完成此操作。其想法是能够编写您自己的 SQL 语句来实现您的业务逻辑。要在**查询**编辑器中使用查询，请执行以下操作。

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

## 删除集群
<a name="rds-delete-cluster"></a>

**重要**  
删除集群是永久性的。在执行此操作之前，请仔细检查您的项目。

删除集群：

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