Expresiones de condición - AWS AppSync GraphQL

Expresiones de condición

Al mutar objetos en DynamoDB utilizando las operaciones de DynamoDB PutItem, UpdateItem y DeleteItem, puede especificar opcionalmente una expresión de condición que determine si la solicitud se debe atender o no en función del estado del objeto que ya está en DynamoDB antes de ejecutar la operación.

La función de DynamoDB de AWS AppSync permite especificar una expresión de condición en los objetos de solicitud PutItem, UpdateItem y DeleteItem, así como la estrategia que debe seguirse en caso de que la condición no se cumpla y el objeto no se actualice.

Ejemplo 1

El siguiente objeto de solicitud PutItem no tiene una expresión de condición. Como resultado, pone un elemento en DynamoDB incluso si ya existe un elemento con la misma clave, lo que permite sobrescribir el elemento existente.

import { util } from '@aws-appsync/utils'; export function request(ctx) { const { foo, bar, ...values} = ctx.args return { operation: 'PutItem', key: util.dynamodb.toMapValues({foo, bar}), attributeValues: util.dynamodb.toMapValues(values), }; }

Ejemplo 2

El siguiente objeto PutItem tiene una expresión de condición que permitirá que la operación se complete solo si no existe un elemento con la misma clave en DynamoDB.

import { util } from '@aws-appsync/utils'; export function request(ctx) { const { foo, bar, ...values} = ctx.args return { operation: 'PutItem', key: util.dynamodb.toMapValues({foo, bar}), attributeValues: util.dynamodb.toMapValues(values), condition: { expression: "attribute_not_exists(id)" } }; }

De forma predeterminada, si se produce un error en la comprobación de condición, la función de DynamoDB de AWS AppSync proporciona un error para la mutación.

Sin embargo, la función de DynamoDB de AWS AppSync ofrece algunas características adicionales para ayudar a los desarrolladores a gestionar algunos casos de periferia habituales:

  • Si las funciones de DynamoDB de AWS AppSync pueden determinar que el valor actual de DynamoDB coincide con el resultado deseado, trata la operación como si se hubiera realizado de todos modos.

  • En lugar de devolver un error, puede configurar la función de modo que invoque una función de Lambda personalizada para decidir cómo debe gestionar el error la función de DynamoDB de AWS AppSync.

Estos casos se describen con más detalle en la sección de gestión de un error de comprobación de la condición.

Para obtener más información sobre las expresiones de condiciones de DynamoDB, consulte la documentación de expresiones de condición de DynamoDB.

Especificación de una condición

Todos los objetos de solicitud PutItem, UpdateItem y DeleteItem permiten especificar una sección condition opcional. Si se omite, no se comprobará ninguna condición. Si se especifica, la condición debe ser true para que la operación se lleve a cabo correctamente.

Las secciones condition tienen la siguiente estructura:

type ConditionCheckExpression = { expression: string; expressionNames?: { [key: string]: string}; expressionValues?: { [key: string]: any}; equalsIgnore?: string[]; consistentRead?: boolean; conditionalCheckFailedHandler?: { strategy: 'Custom' | 'Reject'; lambdaArn?: string; }; };

Los campos siguientes especifican la condición:

expression

La misma expresión de actualización. Para obtener más información sobre cómo escribir expresiones de condición, consulte la documentación de DynamoDB ContitionExpressions. Este campo debe especificarse.

expressionNames

Las sustituciones de los marcadores de posición de nombre de atributo de expresión, en forma de pares de clave-valor. La clave corresponde a un marcador de posición de nombre usado en la expresión, y el valor tiene que ser una cadena que corresponda al nombre de atributo del elemento en DynamoDB. Este campo es opcional y solo debe rellenarse con las sustituciones de los marcadores de posición de nombre de atributo de expresión que se usen en la expresión.

expressionValues

Las sustituciones de los marcadores de posición de valor de atributo de expresión, en forma de pares de clave-valor. La clave corresponde a un marcador de posición de valor usado en la expresión y el valor tiene que ser un valor con tipo. Para obtener más información sobre cómo especificar un “valor con tipo”, consulte Sistema de tipos (mapeo de solicitud). Este valor debe especificarse. Este campo es opcional y solo debe rellenarse con las sustituciones de los marcadores de posición de valor de atributo de expresión que se usen en la expresión.

El resto de los campos indican a la función de DynamoDB de AWS AppSync cómo gestionar los casos en que no se cumpla la condición:

equalsIgnore

Cuando no se cumple la condición para la operación PutItem, la función de DynamoDB de AWS AppSync compara el elemento que hay actualmente en DynamoDB con el elemento que ha intentado escribir. Si son iguales, trata la operación como si se hubiera realizado correctamente. Puede utilizar el campo equalsIgnore para especificar una lista de atributos que AWS AppSync no debe tener en cuenta al realizar la comparación. Por ejemplo, si la única diferencia ha sido un atributo version, trata la operación como si se hubiera realizado satisfactoriamente. Este campo es opcional.

consistentRead

Cuando una condición no se cumple, AWS AppSync obtiene el valor actual del elemento de DynamoDB mediante una lectura altamente coherente. Puede utilizar este campo para indicar a la función de DynamoDB de AWS AppSync que use una lectura coherente posterior en su lugar. Este campo es opcional y de forma predeterminada es true.

conditionalCheckFailedHandler

Esta sección permite especificar la forma en que la función de DynamoDB de AWS AppSync trata los casos en que no se cumpla la condición después de haber comparado el valor actual en DynamoDB con el resultado esperado. Esta sección es opcional. Si se omite, el valor predeterminado es una estrategia Reject.

strategy

La estrategia que la función de DynamoDB de AWS AppSync sigue después de comparar el valor actual en DynamoDB con el resultado esperado. Este campo es obligatorio y tiene los siguientes valores posibles:

Reject

Se produce un error en la mutación y se agrega un error a la respuesta de GraphQL.

Custom

La función de DynamoDB de AWS AppSync invoca una función de Lambda personalizada para decidir cómo gestionar el incumplimiento de la condición. Cuando strategy tiene el valor Custom, el campo lambdaArn debe contener el ARN de la función Lambda que se va a invocar.

lambdaArn

El ARN de la función de Lambda que se invoca que determina cómo debe gestionar la función de DynamoDB de AWS AppSync el incumplimiento de la condición. Este campo solo tiene que especificarse cuando strategy tiene el valor Custom. Para obtener más información acerca de cómo utilizar esta característica, consulte Gestión de un error de comprobación de la condición.

Gestión de un error de comprobación de la condición

Cuando la condición no se cumple, la función de DynamoDB de AWS AppSync puede transferir el error para la mutación y el valor actual del objeto mediante la utilidad util.appendError. Sin embargo, la función de DynamoDB de AWS AppSync ofrece algunas características adicionales para ayudar a los desarrolladores a gestionar algunos casos de periferia habituales:

  • Si las funciones de DynamoDB de AWS AppSync pueden determinar que el valor actual de DynamoDB coincide con el resultado deseado, trata la operación como si se hubiera realizado de todos modos.

  • En lugar de devolver un error, puede configurar la función de modo que invoque una función de Lambda personalizada para decidir cómo debe gestionar el error la función de DynamoDB de AWS AppSync.

El diagrama de flujo de este proceso es:

Flowchart showing process for transforming requests with mutation attempts and value checks.

Comprobación del resultado deseado

Cuando la condición no se cumple, la función de DynamoDB de AWS AppSync realiza una solicitud de DynamoDB GetItem para obtener el valor actual del elemento de DynamoDB. De forma predeterminada, utiliza una lectura altamente coherente; sin embargo, esto puede configurarse mediante el campo consistentRead en el bloque condition y compararlo con el resultado esperado:

  • En la operación PutItem, la función de DynamoDB de AWS AppSync compara el valor actual con el que intentó escribir, excluyendo de la comparación los atributos especificados en equalsIgnore. Si los elementos son los mismos, trata la operación como si se hubiera realizado y devuelve el elemento obtenido de DynamoDB. De lo contrario, sigue la estrategia configurada.

    Por ejemplo, si el objeto de solicitud PutItem tenía el siguiente aspecto:

    import { util } from '@aws-appsync/utils'; export function request(ctx) { const { id, name, version} = ctx.args return { operation: 'PutItem', key: util.dynamodb.toMapValues({foo, bar}), attributeValues: util.dynamodb.toMapValues({ name, version: version+1 }), condition: { expression: "version = :expectedVersion", expressionValues: util.dynamodb.toMapValues({':expectedVersion': version}), equalsIgnore: ['version'] } }; }

    Y el elemento que está actualmente en DynamoDB fuese de la siguiente manera:

    { "id" : { "S" : "1" }, "name" : { "S" : "Steve" }, "version" : { "N" : 8 } }

    La función de DynamoDB de AWS AppSync compararía el elemento que intentó escribir con el valor actual y vería que la única diferencia es el campo version, pero como la configuración pasa el campo version por alto, trataría la operación como correcta y devolvería el elemento obtenido de DynamoDB.

  • En la operación DeleteItem, la función de DynamoDB de AWS AppSync realiza una comprobación para verificar que se ha devuelto un elemento de DynamoDB. Si no se devuelve ningún elemento, trata la operación como si se hubiera realizado correctamente. De lo contrario, sigue la estrategia configurada.

  • En la operación UpdateItem, la función de DynamoDB de AWS AppSync no tiene suficiente información para determinar si el elemento que está actualmente en DynamoDB coincide con el resultado esperado y, por lo tanto, sigue la estrategia configurada.

Si el estado actual del objeto en DynamoDB es diferente del resultado esperado, la función de DynamoDB de AWS AppSync sigue la estrategia configurada para rechazar la mutación o invocar una función de Lambda para determinar qué hacer a continuación.

Aplicación de la estrategia de rechazo

Al seguir la estrategia Reject, la función de DynamoDB de AWS AppSync devuelve un error para la mutación.

Por ejemplo, si se recibe la solicitud de mutación siguiente:

mutation { updatePerson(id: 1, name: "Steve", expectedVersion: 1) { Name theVersion } }

Si el elemento devuelto de DynamoDB tiene un aspecto similar al siguiente:

{ "id" : { "S" : "1" }, "name" : { "S" : "Steve" }, "version" : { "N" : 8 } }

Y el controlador de respuestas de función tiene el siguiente aspecto:

import { util } from '@aws-appsync/utils'; export function response(ctx) { const { version, ...values } = ctx.result; const result = { ...values, theVersion: version }; if (ctx.error) { if (error) { return util.appendError(error.message, error.type, result, null); } } return result }

La respuesta GraphQL tiene este aspecto:

{ "data": null, "errors": [ { "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)" "errorType": "DynamoDB:ConditionalCheckFailedException", ... } ] }

Además, si hay algún campo en el objeto devuelto que hayan cumplimentado otros solucionadores y si la mutación se ha efectuado satisfactoriamente, el objeto no se resuelve cuando se devuelve en la sección error.

Aplicación de la estrategia personalizada

Si se sigue la estrategia Custom, la función de DynamoDB de AWS AppSync invoca una función de Lambda para decidir qué hacer a continuación. La función Lambda selecciona una de las siguientes opciones:

  • reject para la mutación. Esto le indica a la función de DynamoDB de AWS AppSync que se comporte como si la estrategia configurada fuera Reject y que devuelva un error para la mutación y el valor actual del objeto de DynamoDB, tal como se describe en la sección anterior.

  • discard para la mutación. Esto indica a la función de DynamoDB de AWS AppSync que no notifique el incumplimiento de la condición y que devuelva el valor en DynamoDB.

  • retry para la mutación. Esto indica a la función de DynamoDB de AWS AppSync que vuelva a intentar la mutación con un nuevo objeto de solicitud.

La solicitud de invocación Lambda

La función de DynamoDB de AWS AppSync invoca la función de Lambda especificada en el lambdaArn. Se utiliza el mismo service-role-arn configurado en el origen de datos. La carga de la invocación tiene la siguiente estructura:

{ "arguments": { ... }, "requestMapping": {... }, "currentValue": { ... }, "resolver": { ... }, "identity": { ... } }

Los campos se definen de la siguiente manera:

arguments

Los argumentos de la mutación de GraphQL. Esto es lo mismo que los argumentos disponibles en el objeto de solicitud en context.arguments.

requestMapping

El objeto de solicitud de esta operación.

currentValue

El valor actual del objeto en DynamoDB.

resolver

Información sobre el solucionador o la función de AWS AppSync.

identity

Información sobre el intermediario. Equivale a la información de identidad disponible en el objeto de solicitud en context.identity.

Un ejemplo completo de la carga:

{ "arguments": { "id": "1", "name": "Steve", "expectedVersion": 1 }, "requestMapping": { "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : { "S" : "1" } }, "attributeValues" : { "name" : { "S" : "Steve" }, "version" : { "N" : 2 } }, "condition" : { "expression" : "version = :expectedVersion", "expressionValues" : { ":expectedVersion" : { "N" : 1 } }, "equalsIgnore": [ "version" ] } }, "currentValue": { "id" : { "S" : "1" }, "name" : { "S" : "Steve" }, "version" : { "N" : 8 } }, "resolver": { "tableName": "People", "awsRegion": "us-west-2", "parentType": "Mutation", "field": "updatePerson", "outputType": "Person" }, "identity": { "accountId": "123456789012", "sourceIp": "x.x.x.x", "user": "AIDAAAAAAAAAAAAAAAAAA", "userArn": "arn:aws:iam::123456789012:user/appsync" } }

La respuesta de invocación Lambda

La función de Lambda puede inspeccionar la carga de invocación y aplicar cualquier lógica de negocio para decidir la forma en que la función de DynamoDB de AWS AppSync debe gestionar el error. Existen tres opciones para gestionar el error de comprobación de condición:

  • reject para la mutación. La carga de respuesta de esta opción debe tener esta estructura:

    { "action": "reject" }

    Esto indica a la función de DynamoDB de AWS AppSync que se comporte como si la estrategia configurada fuera Reject y que devuelva un error para la mutación y el valor actual del objeto de DynamoDB, tal como se describe en la sección anterior.

  • discard para la mutación. La carga de respuesta de esta opción debe tener esta estructura:

    { "action": "discard" }

    Esto indica a la función de DynamoDB de AWS AppSync que no notifique el incumplimiento de la condición y que devuelva el valor en DynamoDB.

  • retry para la mutación. La carga de respuesta de esta opción debe tener esta estructura:

    { "action": "retry", "retryMapping": { ... } }

    Esto indica a la función de DynamoDB de AWS AppSync que vuelva a intentar la mutación con un nuevo objeto de solicitud. La estructura de la sección retryMapping depende de la operación de DynamoDB y es un subconjunto del objeto de solicitud completo de esa operación.

    Para PutItem, la sección retryMapping tiene la siguiente estructura. Para ver una descripción del campo attributeValues, consulte PutItem.

    { "attributeValues": { ... }, "condition": { "equalsIgnore" = [ ... ], "consistentRead" = true } }

    Para UpdateItem, la sección retryMapping tiene la siguiente estructura. Para ver una descripción de la sección update, consulte UpdateItem.

    { "update" : { "expression" : "someExpression" "expressionNames" : { "#foo" : "foo" }, "expressionValues" : { ":bar" : ... typed value } }, "condition": { "consistentRead" = true } }

    Para DeleteItem, la sección retryMapping tiene la siguiente estructura.

    { "condition": { "consistentRead" = true } }

    No hay forma de especificar otra operación o clave en la que trabajar. La función de DynamoDB de AWS AppSync solo permite reintentos de la misma operación en el mismo objeto. Asimismo, la sección condition no permite especificar un conditionalCheckFailedHandler. Si el reintento fracasa, la función de DynamoDB de AWS AppSync sigue la estrategia Reject.

A continuación se muestra un ejemplo de función Lambda para tratar una solicitud PutItem sin éxito. La lógica de negocio examina quién realizó la llamada. Si la realizó jeffTheAdmin, vuelve a intentar realizar la solicitud, actualizando version y expectedVersion desde el elemento actualmente en DynamoDB. De lo contrario, rechaza la mutación.

exports.handler = (event, context, callback) => { console.log("Event: "+ JSON.stringify(event)); // Business logic goes here. var response; if ( event.identity.user == "jeffTheAdmin" ) { response = { "action" : "retry", "retryMapping" : { "attributeValues" : event.requestMapping.attributeValues, "condition" : { "expression" : event.requestMapping.condition.expression, "expressionValues" : event.requestMapping.condition.expressionValues } } } response.retryMapping.attributeValues.version = { "N" : event.currentValue.version.N + 1 } response.retryMapping.condition.expressionValues[':expectedVersion'] = event.currentValue.version } else { response = { "action" : "reject" } } console.log("Response: "+ JSON.stringify(response)) callback(null, response) };