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.

Configuración de la base de datos de Aurora PostgreSQL

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 de 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 de conexión tardan un tiempo en activarse. Puede comprobar su estado en la consola de RDS, en la pestaña Conectividad y seguridad del clúster.

Comprueba 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

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

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 se ejecuta correctamente, agregue el clúster como origen de datos en la API.

Creación de un esquema de GraphQL

Ahora que la API de datos de Aurora Serverless v2 se ejecuta con tablas configuradas, crearemos un esquema de GraphQL. Puede crear la API con rapidez importando las configuraciones de la tabla desde una base de datos existente mediante el asistente de creación de la 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 Esquema, 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 testdb junto acreateTodo(...): 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 el siguiente solucionador para especificar correctamente el tipo DATE del campo due.

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. Puesto que nuestra base de datos de Postgres puede devolver el ID generado, puede confiar en él para crear y devolver el resultado como una única solicitud de la siguiente forma.

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

Realice el cambio y actualice su esquema. Diríjase al editor Consultas para agregar un elemento a la base de datos de la siguiente forma.

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 controlador de solicitudes de la siguiente forma.

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 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 de la siguiente forma el elemento si la descripción comienza por edits.

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 en updateTodo de la siguiente forma.

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. Funciona igual que la mutación updateTodo, y debe especificar el id del elemento que desea eliminar de la siguiente forma.

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 Todo de la siguiente forma.

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 campo nuevo de su tipo Query denominado getTodoAndTasks de la siguiente forma.

getTodoAndTasks(id: Int!): Todo

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

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