教程:带有数据 API 的 Aurora PostgreSQL - Amazon AppSync
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

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

教程:带有数据 API 的 Aurora PostgreSQL

Amazon AppSync 提供了一个数据来源,用于针对使用数据 API 启用的 Amazon Aurora 集群执行 SQL 语句。您可以使用 Amazon AppSync 解析程序通过 GraphQL 查询、更改和订阅对数据 API 运行 SQL 语句。

注意

本教程使用 US-EAST-1 区域。

创建集群

在将 Amazon RDS 数据来源添加到 Amazon AppSync 之前,先在 Aurora Serverless 集群上启用数据 API。您还必须使用 Amazon Secrets Manager 配置密钥。要创建 Aurora Serverless 集群,您可以使用 Amazon CLI:

aws rds create-db-cluster \ --db-cluster-identifier appsync-tutorial \ --engine aurora-postgresql --engine-version 13.11 \ --engine-mode serverless \ --master-username USERNAME \ --master-user-password COMPLEX_PASSWORD

这将为集群返回一个 ARN。您可以使用以下命令检查集群的状态:

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

使用上一步中的 USERNAMECOMPLEX_PASSWORD 通过 Amazon Secrets Manager 控制台创建一个密钥,或者通过 Amazon CLI 使用如下输入文件创建密钥:

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

将其作为参数传递给 CLI:

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

这将为密钥返回 ARN。记下 Aurora Serverless 集群的 ARN 和密钥,稍后在 Amazon AppSync 控制台中创建数据来源时需要使用。

启用数据 API

集群状态更改为 available 后,请按照 Amazon RDS 文档启用数据 API。在将数据 API 添加为 Amazon AppSync 数据来源之前,必须先启用它。还可以使用 Amazon CLI 启用数据 API。

aws rds modify-db-cluster \ --db-cluster-identifier appsync-tutorial \ --enable-http-endpoint \ --apply-immediately

创建数据库和表

启用您的数据 API 后,请使用 Amazon CLI 中的 aws rds-data execute-statement 命令来验证它是否正常运行。这可以确保在将 Aurora Serverless 集群添加到 Amazon AppSync API 之前,对其进行正确配置。首先,使用 --sql 参数创建 TESTDB 数据库:

aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:123456789012:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:123456789012:secret:appsync-tutorial-rds-secret" \ --sql "create DATABASE \"testdb\""

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

aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:123456789012:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:123456789012: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:123456789012:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:123456789012: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 架构

现在,您的 Aurora Serverless 数据 API 正在使用已配置的表运行,我们将创建一个 GraphQL 架构。您可以手动执行此操作,但 Amazon AppSync 可让您通过使用 API 创建向导从现有数据库导入表配置,从而快速入门。

首先:

  1. 在 Amazon AppSync 控制台中,选择创建 API,然后选择以 Amazon Aurora 集群开始

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

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

  4. 选择您的密钥,然后选择导入

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

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

    type Todo { id: Int! description: String! due: AWSDate! createdAt: String } type Task { id: Int! todoId: Int! description: String }
  7. 对于角色,您可以让 Amazon AppSync 创建新角色,也可以使用与以下策略类似的策略创建角色:

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

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

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

RDS 的解析器

API 创建流程会自动创建解析器来与我们的类型进行交互。如果您查看架构页面,您会发现执行以下操作所需的解析器:

  • 通过 Mutation.createTodo 字段创建 todo

  • 通过 Mutation.updateTodo 字段更新 todo

  • 通过 Mutation.deleteTodo 字段删除 todo

  • 通过 Query.getTodo 字段获取单个 todo

  • 通过 Query.listTodos 字段列出所有 todos

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

Mutation.createTodo

在 Amazon AppSync 控制台的架构编辑器中,在右侧选择 createTodo(...): Todo 旁边的 testdb。解析器代码使用 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

在控制台的架构编辑器中,在右侧选择 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

您也可以对 Todo 执行 update。从查询编辑器中,我们来更新第一个 Todo 项目(id1)。

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

就像我们处理 createlist 操作的方式一样,我们可以更新解析器以将 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

您可以通过 deleteTodo 突变对 Todo 执行 delete。这就像 updateTodo 突变一样,您必须指定要删除的项目的 id

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

编写自定义查询

我们已经使用 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 } } } }

删除集群

重要

删除集群是永久性的。在执行此操作之前,请仔细检查您的项目。

删除集群:

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