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

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

在 Amazon AppSync中使用带有数据 API 的 Aurora PostgreSQL

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

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

注意

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

设置你的 Aurora PostgreSQL 数据库

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

  1. 在 Aurora 无服务器 v2 集群上启用数据 API。

  2. 使用配置密钥 Amazon Secrets Manager

  3. 使用以下 Amazon 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。创建集群后,必须使用以下 Amazon 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 控制台的集群连接和安全选项卡中查看它们的状态。

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

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

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

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

将其作为参数传递给 Amazon CLI:

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

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

创建数据库和表

首先,创建一个名为的数据库TESTDB。在 PostgreSQL 中,数据库是存放表和其他 SQL 对象的容器。在将 Aurora Serverless v2 集群添加到 API 之前,请 Amazon AppSync 验证其配置是否正确。首先,使用如下--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 架构

现在,您的 Aurora Serverless v2 数据 API 正在使用已配置的表运行,我们将创建一个 GraphQL 架构。您可以使用 API 创建向导从现有数据库导入表配置,从而快速创建 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 创建一个新角色,也可以使用类似于以下策略的策略创建一个角色:

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

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

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

RDS 的解析器

API 创建流程会自动创建解析器来与我们的类型进行交互。如果你查看 Schema 页面,你会发现解析器有以下一些解析器。

  • 通过 Mutation.createTodo 字段创建 todo

  • 通过 Mutation.updateTodo 字段更新 todo

  • 通过 Mutation.deleteTodo 字段删除 todo

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

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

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

Mutation.createTodo

在 Amazon 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! }

进行更改并更新您的架构。前往 Querie s 编辑器向数据库中添加一个项目,如下所示。

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