Uso de Aurora PostgreSQL con la API de datos en AWS AppSync - AWS AppSync GraphQL

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Uso de Aurora PostgreSQL con la API de datos en AWS AppSync

Aprenda a conectar su API de GraphQL a las bases de datos de Aurora PostgreSQL mediante. AWS AppSync Esta integración le permite crear aplicaciones escalables y basadas en datos mediante la ejecución de consultas y mutaciones de SQL mediante operaciones de GraphQL. AWS AppSync proporciona una fuente de datos para ejecutar sentencias SQL en los clústeres de Amazon Aurora que están habilitados con una API de datos. Puedes usar AWS AppSync resolutores para ejecutar sentencias SQL en la API de datos con consultas, mutaciones y suscripciones de GraphQL.

Antes de comenzar este tutorial, debes tener una familiaridad básica con los AWS servicios y los conceptos de GraphQL.

nota

En este tutorial se utiliza la región US-EAST-1.

Configure su base de datos PostgreSQL de Aurora

Antes de añadir una fuente de datos de Amazon RDS a AWS AppSync, haga lo siguiente.

  1. Habilite una API de datos en un clúster Aurora Serverless v2.

  2. Configure un secreto mediante AWS Secrets Manager

  3. Cree el clúster mediante el siguiente AWS CLI comando.

    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

Esto devolverá un ARN para el clúster. Tras crear un clúster, debes añadir una instancia Serverless v2 con el siguiente AWS CLI comando.

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
nota

Estos puntos finales tardan un tiempo en activarse. Puede comprobar su estado en la consola de RDS, en la pestaña Conectividad y seguridad del clúster.

Compruebe el estado del clúster con el siguiente AWS CLI comando.

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

Cree un secreto a través de la AWS Secrets Manager consola o AWS CLI con un archivo de entrada como el siguiente mediante USERNAME y COMPLEX_PASSWORD desde el paso anterior:

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

Pase esto como parámetro a AWS CLI:

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

Esto devolverá un ARN para el secreto. Al crear una fuente de datos en la consola, anote el ARN del clúster Aurora Serverless v2 y el secreto para más adelante. AWS AppSync

Creación de la base de datos y la tabla

En primer lugar, cree una base de datos con el nombre. TESTDB En PostgreSQL, una base de datos es un contenedor que contiene tablas y otros objetos SQL. Compruebe que el clúster de Aurora Serverless v2 esté configurado correctamente antes de añadirlo a la AWS AppSync API. En primer lugar, cree una base de datos TESTDB con el --sql siguiente parámetro.

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"

Si esto se ejecuta sin errores, añada dos tablas con el comando 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);'

Si tiene éxito, añada el clúster como fuente de datos en su API.

Creación de un esquema de GraphQL

Ahora que la API de datos Aurora Serverless v2 se está ejecutando con tablas configuradas, crearemos un esquema de GraphQL. Puede crear su API rápidamente importando las configuraciones de tablas de una base de datos existente mediante el asistente de creación de API.

Para empezar:

  1. En la AWS AppSync consola, elija Crear API y, a continuación, Comenzar con un clúster de Amazon Aurora.

  2. Especifique los detalles de la API, como el nombre de la API, y, a continuación, seleccione su base de datos para generar la API.

  3. Seleccione la base de datos. Si es necesario, actualice la región y, a continuación, elija el clúster de Aurora y la base de datos TESTDB.

  4. Elija su secreto y, a continuación, seleccione Importar.

  5. Una vez descubiertas las tablas, actualice los nombres de tipos. Cambie Todos a Todo y Tasks a Task.

  6. Obtenga una vista previa del esquema generado seleccionando Vista previa del esquema. Sus esquema tendrá un aspecto similar al siguiente:

    type Todo { id: Int! description: String! due: AWSDate! createdAt: String } type Task { id: Int! todoId: Int! description: String }
  7. Para el rol, puede AWS AppSync crear un rol nuevo o crear uno con una política similar a la siguiente:

    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:*" ] } ] }

    Tenga en cuenta que hay dos instrucciones en esta política a las que está concediendo acceso de rol. El primer recurso es su clúster de Aurora y el segundo es su ARN de AWS Secrets Manager .

    Elija Siguiente, revise los detalles de configuración y, a continuación, elija Crear API. Ahora puede disponer de una API totalmente operativa. Puede revisar todos los detalles de su API en la página Esquema.

Solucionadores para RDS

El flujo de creación de la API creó automáticamente los solucionadores para que interactuaran con nuestros tipos. Si consulta la página de esquemas, encontrará algunos de los siguientes solucionadores.

  • Crear un todo mediante el campo Mutation.createTodo.

  • Actualizar un todo mediante el campo Mutation.updateTodo.

  • Eliminar un todo mediante el campo Mutation.deleteTodo.

  • Obtener un todo individual mediante el campo Query.getTodo.

  • Enumerar todos todos mediante el campo Query.listTodos.

Encontrará campos y solucionadores similares adjuntos para el tipo Task. Echemos un vistazo más de cerca a algunos de los solucionadores.

Mutation.createTodo

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, selecciona junto a. testdb createTodo(...): Todo El código de resolución utiliza la función insert del módulo rds para crear dinámicamente una instrucción de inserción que añade datos a la tabla todos. Puesto que estamos trabajando con Postgres, podemos aprovechar la instrucción returning para recuperar los datos insertados.

Actualice la siguiente resolución para especificar correctamente el DATE tipo de due campo.

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

Guarde el solucionador. La sugerencia de tipo marca el due correctamente en nuestro objeto de entrada como un tipo DATE. Esto permite que el motor Postgres interprete correctamente el valor. A continuación, actualice su esquema para eliminar el id de la entrada CreateTodo. Como nuestra base de datos de Postgres puede devolver el ID generado, puede confiar en él para crear y devolver el resultado en una sola solicitud, de la siguiente manera.

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

Realice el cambio y actualice su esquema. Dirígete al editor de consultas para añadir un elemento a la base de datos de la siguiente manera.

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

Obtendrá el siguiente resultado.

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

Query.listTodos

En el editor de esquemas de la consola, en el lado derecho, elija testdb junto a listTodos(id: ID!): Todo. El controlador de solicitudes utiliza la función de utilidad select para crear una solicitud de forma dinámica en tiempo de ejecución.

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

Queremos filtrar todos en función de la fecha due. Actualicemos la resolución para convertir los valores due en DATE. Actualice la lista de importaciones y el gestor de solicitudes de la siguiente manera.

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

En el editor de consultas, haga lo siguiente.

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

Mutation.updateTodo

También puede update a Todo. En el editor de consultas, vamos a actualizar nuestro primer elemento Todo de id 1.

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

Tenga en cuenta que debe especificar el id del elemento que está actualizando. También puede especificar una condición para actualizar únicamente un elemento que cumpla condiciones específicas. Por ejemplo, es posible que solo queramos editar el elemento si la descripción comienza de la edits siguiente manera.

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

Al igual que gestionamos nuestras operaciones create y list, podemos actualizar nuestro solucionador para convertir el campo due en una DATE. Guarde estos cambios de la updateTodo siguiente manera.

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

Ahora intente realizar una actualización con una condición:

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

Puede delete un Todo con la mutación deleteTodo. Esto funciona igual que la updateTodo mutación y debes especificar el elemento que deseas eliminar id de la siguiente manera.

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

Escritura de consultas personalizadas

Hemos utilizado las utilidades del módulo rds para crear nuestras instrucciones SQL. También podemos escribir nuestra propia instrucción estática personalizada para interactuar con nuestra base de datos. En primer lugar, actualice el esquema para eliminar el campo id de la entrada CreateTask.

input CreateTaskInput { todoId: Int! description: String }

A continuación, cree un par de tareas. Una tarea tiene una relación de clave externa con la Todo siguiente.

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

Cree un nuevo campo de su Query tipo denominado de la getTodoAndTasks siguiente manera.

getTodoAndTasks(id: Int!): Todo

Agregue un tasks campo al Todo tipo de la siguiente manera.

type Todo { due: AWSDate! id: Int! createdAt: String description: String! tasks:TaskConnection }

Guarde el esquema. En el editor de esquemas de la consola, elija a la derecha Asociar solucionador para getTodosAndTasks(id: Int!): Todo. Elija el origen de datos de Amazon RDS. Actualice su resolución con el siguiente código.

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

En este código, utilizamos la plantilla de etiquetas de sql para escribir una instrucción SQL a la que podamos pasar un valor dinámico de forma segura en tiempo de ejecución. createPgStatement puede aceptar hasta dos solicitudes de SQL a la vez. La usamos para enviar una consulta para nuestra todo y otra para nuestras tasks. Podría haberlo hecho con una instrucción JOIN o con cualquier otro método. La idea es poder escribir su propia instrucción SQL para implementar su lógica empresarial. Para usar la consulta en el editor de consultas, haga lo siguiente.

query TodoAndTasks { getTodosAndTasks(id: 2) { id due description tasks { items { id description } } } }

Eliminación de su clúster

importante

La eliminación de un clúster es permanente. Revise su proyecto detenidamente antes de llevar a cabo esta acción.

Para eliminar el clúster:

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