条件表达式 - AWS AppSync

条件表达式

在您使用 PutItemUpdateItemDeleteItem DynamoDB 操作变更 DynamoDB 中的对象时,您可以选择指定一个条件表达式,以根据执行操作之前 DynamoDB 中的已有对象状态控制请求是否应成功。

AWS AppSync DynamoDB 函数允许在 PutItemUpdateItemDeleteItem 请求对象中指定条件表达式,并提供在条件失败并且未更新对象时遵循的策略。

示例 1

以下 PutItem 请求对象没有条件表达式。因此,即使已存在具有相同键的项目,它也会将项目放置在 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), }; }

示例 2

以下 PutItem 对象确实具有一个条件表达式,只有在 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)" } }; }

默认情况下,如果条件检查失败,则 AWS AppSync DynamoDB 函数在 ctx.error 中提供一个错误。您可以返回变更错误,并在 GraphQL 响应 error 部分的 data 字段中返回 DynamoDB 中的对象的当前值。

不过,AWS AppSync DynamoDB 函数提供了一些额外的功能,以帮助开发人员处理一些常见的边缘情况:

  • 如果 AWS AppSync DynamoDB 函数可以确定 DynamoDB 中的当前值与所需的结果匹配,则会将该操作视为成功。

  • 您可以将函数配置为调用自定义 Lambda 函数以决定 AWS AppSync DynamoDB 函数应如何处理失败,而不是返回错误。

处理条件检查失败一节中更详细地介绍了这些内容。

有关 DynamoDB 条件表达式的更多信息,请参阅 DynamoDB 条件表达式文档

指定条件

PutItemUpdateItemDeleteItem 请求对象都允许指定可选的 condition 部分。如果省略,则不会进行条件检查。如果指定,条件必须为 true,操作才能成功。

condition 部分具有以下结构:

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

下列字段指定条件:

expression

更新表达式本身。有关如何编写条件表达式的更多信息,请参阅 DynamoDB ConditionExpressions 文档。必须指定该字段。

expressionNames

以键值对形式替换表达式属性名称占位符。键对应于 expression 中使用的名称占位符,值必须是与 DynamoDB 中的项目的属性名称对应的字符串。该字段是可选的,只应填充 expression 中使用的表达式属性名称占位符的替换内容。

expressionValues

以键值对形式替换表达式属性值占位符。键对应于 expression 中使用的值占位符,而值必须为类型化值。有关如何指定“类型化值”的更多信息,请参阅类型系统(请求映射)。必须指定此值。该字段是可选的,只应填充 expression 中使用的表达式属性值占位符的替换内容。

其余字段指示 AWS AppSync DynamoDB 函数如何处理条件检查失败:

equalsIgnore

如果在使用 PutItem 操作时条件检查失败,则 AWS AppSync DynamoDB 函数将当前位于 DynamoDB 中的项目与它尝试写入的项目进行比较。如果两者相同,则它会将操作视为已成功。您可以使用 equalsIgnore 字段指定 AWS AppSync 在执行该比较时应忽略的属性列表。例如,如果唯一的区别是 version 属性,则会将操作视为成功。该字段是可选的。

consistentRead

在条件检查失败时,AWS AppSync 使用强一致性读取从 DynamoDB 中获取项目的当前值。您可以使用该字段指示 AWS AppSync DynamoDB 函数改用最终一致性读取。该字段是可选的,默认值为 true

conditionalCheckFailedHandler

在该部分中,您可以指定 AWS AppSync DynamoDB 函数在将 DynamoDB 中的当前值与预期结果进行比较后如何处理条件检查失败。此部分是可选的。如果省略,它默认为 Reject 策略。

strategy

AWS AppSync DynamoDB 函数在将 DynamoDB 中的当前值与预期结果进行比较后采取的策略。该字段为必填字段,有以下可能的值:

Reject

变更失败,将返回变更错误,并在 GraphQL 响应 error 部分的 data 字段中返回 DynamoDB 中的对象的当前值。

Custom

AWS AppSync DynamoDB 函数调用自定义 Lambda 函数,以决定如何处理条件检查失败。当 strategy 设置为 Custom 时,lambdaArn 字段必须包含要调用的 Lambda 函数的 ARN。

lambdaArn

要调用的 Lambda 函数的 ARN,用于确定 AWS AppSync DynamoDB 函数应如何处理条件检查失败。当 strategy 设置为 Custom 时,必须指定该字段。有关如何使用该功能的更多信息,请参阅处理条件检查失败

处理条件检查失败

在条件检查失败时,AWS AppSync DynamoDB 函数可以使用 util.appendError 实用程序传递变更错误和对象的当前值。这会在 GraphQL 响应的 error 部分中添加 data 字段。不过,AWS AppSync DynamoDB 函数提供了一些额外的功能,以帮助开发人员处理一些常见的边缘情况:

  • 如果 AWS AppSync DynamoDB 函数可以确定 DynamoDB 中的当前值与所需的结果匹配,则会将该操作视为成功。

  • 您可以将函数配置为调用自定义 Lambda 函数以决定 AWS AppSync DynamoDB 函数应如何处理失败,而不是返回错误。

此过程的流程图为:

检查所需的结果

在条件检查失败时,AWS AppSync DynamoDB 函数执行 GetItem DynamoDB 请求,以从 DynamoDB 中获取项目的当前值。默认情况下,它将使用强一致性读取,但这可以使用 condition 数据块中的 consistentRead 字段进行配置,并将当前值与预期结果进行比较:

  • 对于 PutItem 操作,AWS AppSync DynamoDB 函数将当前值与它尝试写入的值进行比较,以从比较中排除 equalsIgnore 中列出的任何属性。如果项目相同,则将操作视为成功并返回从 DynamoDB 中检索的项目。否则,它将遵循所配置的策略。

    例如,如果 PutItem 请求对象如下所示:

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

    当前位于 DynamoDB 中的项目如下所示:

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

    AWS AppSync DynamoDB 函数将它尝试写入的项目与当前值进行比较,发现唯一的区别是 version 字段,但由于它配置为忽略 version 字段,因此,将操作视为成功并返回从 DynamoDB 中检索的项目。

  • 对于 DeleteItem 操作,AWS AppSync DynamoDB 函数检查以验证是否从 DynamoDB 返回了项目。如果没有返回项目,它会将该操作视为已成功。否则,它将遵循所配置的策略。

  • 对于 UpdateItem 操作,AWS AppSync DynamoDB 函数没有足够的信息,无法确定当前位于 DynamoDB 中的项目是否与预期结果匹配,因此,将遵循配置的策略。

如果 DynamoDB 中的对象的当前状态与预期结果不同,则 AWS AppSync DynamoDB 函数按照配置的策略拒绝变更或调用 Lambda 函数以确定后续操作。

遵循“reject”策略

在遵循 Reject 策略时,AWS AppSync DynamoDB 函数返回变更错误,并且还会在 GraphQL 响应 error 部分的 data 字段中返回 DynamoDB 中的对象的当前值。从 DynamoDB 返回的项目通过函数响应处理程序转换为客户端的预期格式,并按选择集进行筛选。

例如,给定以下变更请求:

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

如果从 DynamoDB 返回的项目如下所示:

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

函数响应处理程序如下所示:

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 }

GraphQL 响应如下所示:

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

另外,如果返回的对象中的任何字段在变更成功后已由其他解析器填充,则当在 error 部分中返回此对象时,将不会对这些字段进行解析。

遵循“custom”策略

在遵循 Custom 策略时,AWS AppSync DynamoDB 函数调用 Lambda 函数以决定后续操作。Lambda 函数选择下列选项之一:

  • reject 变更。这会指示 AWS AppSync DynamoDB 函数的行为就像配置的策略是 Reject 一样,返回变更错误和 DynamoDB 中的对象的当前值,如上一节所述。

  • discard 变更。这会指示 AWS AppSync DynamoDB 函数静默忽略条件检查失败并返回 DynamoDB 中的值。

  • retry 变更。这会指示 AWS AppSync DynamoDB 函数使用新的请求对象重试变更。

Lambda 调用请求

AWS AppSync DynamoDB 函数调用 lambdaArn 中指定的 Lambda 函数。它将使用在数据来源上配置的相同 service-role-arn。调用的负载具有以下结构:

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

字段定义如下:

arguments

来自 GraphQL 变更的参数。这与 context.arguments 中的请求对象的可用参数相同。

requestMapping

该操作的请求对象。

currentValue

DynamoDB 中的对象的当前值。

resolver

有关 AWS AppSync 解析器或函数的信息。

identity

有关调用方的信息。这与 context.identity 中的请求对象的可用身份信息相同。

负载的完整示例:

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

Lambda 调用响应

Lambda 函数可以检查调用负载,并应用任何业务逻辑以决定 AWS AppSync DynamoDB 函数应如何处理失败。有三种选项可用于处理条件检查失败:

  • reject 变更。此选项的响应负载必须具有此结构:

    { "action": "reject" }

    这会指示 AWS AppSync DynamoDB 解析器的行为就像配置的策略是 Reject 一样,返回变更错误和 DynamoDB 中的对象的当前值,如上一节所述。

  • discard 变更。此选项的响应负载必须具有此结构:

    { "action": "discard" }

    这会指示 AWS AppSync DynamoDB 函数静默忽略条件检查失败并返回 DynamoDB 中的值。

  • retry 变更。此选项的响应负载必须具有此结构:

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

    这会指示 AWS AppSync DynamoDB 函数使用新的请求对象重试变更。retryMapping 部分的结构取决于 DynamoDB 操作,并且是该操作的完整请求对象的子集。

    对于 PutItemretryMapping 部分具有以下结构。有关 attributeValues 字段的描述,请参阅 PutItem

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

    对于 UpdateItemretryMapping 部分具有以下结构。有关 update 部分的说明,请参阅 UpdateItem

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

    对于 DeleteItemretryMapping 部分具有以下结构。

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

    无法指定要使用的不同的操作或键。AWS AppSync DynamoDB 函数仅允许对同一对象重试相同操作。另外,condition 部分不允许指定 conditionalCheckFailedHandler。如果重试失败,AWS AppSync DynamoDB 函数将遵循 Reject 策略。

在下面的示例中,Lambda 函数处理失败的 PutItem 请求。业务逻辑将查看进行调用的用户。如果调用是由 jeffTheAdmin 进行的,则会重试该请求,并从当前位于 DynamoDB 的项目中更新 versionexpectedVersion。否则,业务逻辑将拒绝变更。

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