Condition expressions
When you mutate objects in DynamoDB by using the PutItem
, UpdateItem
, and
DeleteItem
DynamoDB operations, you can optionally specify a condition expression that controls
whether the request should succeed or not, based on the state of the object already in DynamoDB before the
operation is performed.
The AWS AppSync DynamoDB resolver allows a condition expression to be specified in PutItem
,
UpdateItem
, and DeleteItem
request mapping documents, and also a strategy to
follow if the condition fails and the object was not updated.
Example 1
The following PutItem
mapping document doesn’t have a condition expression. As a result, it
puts an item in DynamoDB even if an item with the same key already exists, thereby overwriting the existing
item.
{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : { "S" : "1" } } }
Example 2
The following PutItem
mapping document does have a condition expression that allows the
operation succeed only if an item with the same key does not exist in DynamoDB.
{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : { "S" : "1" } }, "condition" : { "expression" : "attribute_not_exists(id)" } }
By default, if the condition check fails, the AWS AppSync DynamoDB resolver returns an error for the mutation and
the current value of the object in DynamoDB in a data
field in the error
section of
the GraphQL response. However, the AWS AppSync DynamoDB resolver offers some additional features to help developers
handle some common edge cases:
-
If AWS AppSync DynamoDB resolver can determine that the current value in DynamoDB matches the desired result, it treats the operation as if it succeeded anyway.
-
Instead of returning an error, you can configure the resolver to invoke a custom Lambda function to decide how the AWS AppSync DynamoDB resolver should handle the failure.
These are described in greater detail in the Handling a Condition Check Failure section.
For more information about DynamoDB conditions expressions, see the DynamoDB ConditionExpressions documentation .
Specifying a condition
The PutItem
, UpdateItem
, and DeleteItem
request mapping documents
all allow an optional condition
section to be specified. If omitted, no condition check is
made. If specified, the condition must be true for the operation to succeed.
A condition
section has the following structure:
"condition" : { "expression" : "someExpression" "expressionNames" : { "#foo" : "foo" }, "expressionValues" : { ":bar" : ... typed value }, "equalsIgnore" : [ "version" ], "consistentRead" : true, "conditionalCheckFailedHandler" : { "strategy" : "Custom", "lambdaArn" : "arn:..." } }
The following fields specify the condition:
-
expression
-
The update expression itself. For more information about how to write condition expressions, see the DynamoDB ConditionExpressions documentation . This field must be specified.
-
expressionNames
-
The substitutions for expression attribute name placeholders, in the form of key-value pairs. The key corresponds to a name placeholder used in the expression, and the value must be a string corresponding to the attribute name of the item in DynamoDB. This field is optional, and should only be populated with substitutions for expression attribute name placeholders used in the expression.
-
expressionValues
-
The substitutions for expression attribute value placeholders, in the form of key-value pairs. The key corresponds to a value placeholder used in the expression, and the value must be a typed value. For more information about how to specify a “typed value”, see Type System (Request Mapping). This must be specified. This field is optional, and should only be populated with substitutions for expression attribute value placeholders used in the expression.
The remaining fields tell the AWS AppSync DynamoDB resolver how to handle a condition check failure:
-
equalsIgnore
-
When a condition check fails when using the
PutItem
operation, the AWS AppSync DynamoDB resolver compares the item currently in DynamoDB against the item it tried to write. If they are the same, it treats the operation as it if succeeded anyway. You can use theequalsIgnore
field to specify a list of attributes that AWS AppSync should ignore when performing that comparison. For example, if the only difference was aversion
attribute, it treats the operation as if it succeeded. This field is optional. -
consistentRead
-
When a condition check fails, AWS AppSync gets the current value of the item from DynamoDB using a strongly consistent read. You can use this field to tell the AWS AppSync DynamoDB resolver to use an eventually consistent read instead. This field is optional, and defaults to
true
. -
conditionalCheckFailedHandler
-
This section allows you to specify how the AWS AppSync DynamoDB resolver treats a condition check failure after it has compared the current value in DynamoDB against the expected result. This section is optional. If omitted, it defaults to a strategy of
Reject
.-
strategy
-
The strategy the AWS AppSync DynamoDB resolver takes after it has compared the current value in DynamoDB against the expected result. This field is required and has the following possible values:
-
Reject
-
The mutation fails, and an error for the mutation and the current value of the object in DynamoDB in a
data
field in theerror
section of the GraphQL response. -
Custom
-
The AWS AppSync DynamoDB resolver invokes a custom Lambda function to decide how to handle the condition check failure. When the
strategy
is set toCustom
, thelambdaArn
field must contain the ARN of the Lambda function to invoke.
-
-
lambdaArn
-
The ARN of the Lambda function to invoke that determines how the AWS AppSync DynamoDB resolver should handle the condition check failure. This field must only be specified when
strategy
is set toCustom
. For more information about how to use this feature, see Handling a Condition Check Failure.
-
Handling a condition check failure
By default, when a condition check fails, the AWS AppSync DynamoDB resolver returns an error for the mutation and
the current value of the object in DynamoDB in a data
field in the error
section of
the GraphQL response. However, the AWS AppSync DynamoDB resolver offers some additional features to help developers
handle some common edge cases:
-
If AWS AppSync DynamoDB resolver can determine that the current value in DynamoDB matches the desired result, it treats the operation as if it succeeded anyway.
-
Instead of returning an error, you can configure the resolver to invoke a custom Lambda function to decide how the AWS AppSync DynamoDB resolver should handle the failure.
The flowchart for this process is:
Checking for the desired result
When the condition check fails, the AWS AppSync DynamoDB resolver performs a GetItem
DynamoDB
request to get the current value of the item from DynamoDB. By default, it uses a strongly consistent read,
however this can be configured using the consistentRead
field in the condition
block and compare it against the expected result:
-
For the
PutItem
operation, the AWS AppSync DynamoDB resolver compares the current value against the one it attempted to write, excluding any attributes listed inequalsIgnore
from the comparison. If the items are the same, it treats the operation as successful and returns the item that was retrieved from DynamoDB. Otherwise, it follows the configured strategy.For example, if the
PutItem
request mapping document looked like the following:{ "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" ] } }
And the item currently in DynamoDB looked like the following:
{ "id" : { "S" : "1" }, "name" : { "S" : "Steve" }, "version" : { "N" : 8 } }
The AWS AppSync DynamoDB resolver would compare the item it tried to write against the current value, see that the only difference was the
version
field, but because it’s configured to ignore theversion
field, it treats the operation as successful and returns the item that was retrieved from DynamoDB. -
For the
DeleteItem
operation, the AWS AppSync DynamoDB resolver checks to verify that an item was returned from DynamoDB. If no item was returned, it treats the operation as successful. Otherwise, it follows the configured strategy. -
For the
UpdateItem
operation, the AWS AppSync DynamoDB resolver does not have enough information to determine if the item currently in DynamoDB matches the expected result, and therefore follows the configured strategy.
If the current state of the object in DynamoDB is different from the expected result, the AWS AppSync DynamoDB resolver follows the configured strategy, to either reject the mutation or invoke a Lambda function to determine what to do next.
Following the “reject” strategy
When following the Reject
strategy, the AWS AppSync DynamoDB resolver returns an error for the
mutation, and the current value of the object in DynamoDB is also returned in a data
field in
the error
section of the GraphQL response. The item returned from DynamoDB is put through the
response mapping template to translate it into a format the client expects, and it is filtered by the
selection set.
For example, given the following mutation request:
mutation { updatePerson(id: 1, name: "Steve", expectedVersion: 1) { Name theVersion } }
If the item returned from DynamoDB looks like the following:
{ "id" : { "S" : "1" }, "name" : { "S" : "Steve" }, "version" : { "N" : 8 } }
And the response mapping template looks like the following:
{ "id" : $util.toJson($context.result.id), "Name" : $util.toJson($context.result.name), "theVersion" : $util.toJson($context.result.version) }
The GraphQL response looks like the following:
{ "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 }, ... } ] }
Also, if any fields in the returned object are filled by other resolvers and the mutation had
succeeded, they won’t be resolved when the object is returned in the error
section.
Following the “custom” strategy
When following the Custom
strategy, the AWS AppSync DynamoDB resolver invokes a Lambda function
to decide what to do next. The Lambda function chooses one of the following options:
-
reject
the mutation. This tells the AWS AppSync DynamoDB resolver to behave as if the configured strategy wasReject
, returning an error for the mutation and the current value of the object in DynamoDB as described in the previous section. -
discard
the mutation. This tells the AWS AppSync DynamoDB resolver to silently ignore the condition check failure and returns the value in DynamoDB. -
retry
the mutation. This tells the AWS AppSync DynamoDB resolver to retry the mutation with a new request mapping document.
The Lambda invocation request
The AWS AppSync DynamoDB resolver invokes the Lambda function specified in the lambdaArn
. It uses
the same service-role-arn
configured on the data source. The payload of the invocation has
the following structure:
{ "arguments": { ... }, "requestMapping": {... }, "currentValue": { ... }, "resolver": { ... }, "identity": { ... } }
The fields are defined as follows:
-
arguments
-
The arguments from the GraphQL mutation. This is the same as the arguments available to the request mapping document in
$context.arguments
. -
requestMapping
-
The request mapping document for this operation.
-
currentValue
-
The current value of the object in DynamoDB.
-
resolver
-
Information about the AWS AppSync resolver.
-
identity
-
Information about the caller. This is the same as the identity information available to the request mapping document in
$context.identity
.
A full example of the payload:
{ "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" } }
The Lambda Invocation Response
The Lambda function can inspect the invocation payload and apply any business logic to decide how the AWS AppSync DynamoDB resolver should handle the failure. There are three options for handling the condition check failure:
-
reject
the mutation. The response payload for this option must have this structure:{ "action": "reject" }
This tells the AWS AppSync DynamoDB resolver to behave as if the configured strategy was
Reject
, returning an error for the mutation and the current value of the object in DynamoDB, as described in the section above. -
discard
the mutation. The response payload for this option must have this structure:{ "action": "discard" }
This tells the AWS AppSync DynamoDB resolver to silently ignore the condition check failure and returns the value in DynamoDB.
-
retry
the mutation. The response payload for this option must have this structure:{ "action": "retry", "retryMapping": { ... } }
This tells the AWS AppSync DynamoDB resolver to retry the mutation with a new request mapping document. The structure of the
retryMapping
section depends on the DynamoDB operation, and is a subset of the full request mapping document for that operation.For
PutItem
, theretryMapping
section has the following structure. For a description of theattributeValues
field, see PutItem.{ "attributeValues": { ... }, "condition": { "equalsIgnore" = [ ... ], "consistentRead" = true } }
For
UpdateItem
, theretryMapping
section has the following structure. For a description of theupdate
section, see UpdateItem.{ "update" : { "expression" : "someExpression" "expressionNames" : { "#foo" : "foo" }, "expressionValues" : { ":bar" : ... typed value } }, "condition": { "consistentRead" = true } }
For
DeleteItem
, theretryMapping
section has the following structure.{ "condition": { "consistentRead" = true } }
There is no way to specify a different operation or key to work on. The AWS AppSync DynamoDB resolver only allows retries of the same operation on the same object. Also, the
condition
section doesn’t allow aconditionalCheckFailedHandler
to be specified. If the retry fails, the AWS AppSync DynamoDB resolver follows theReject
strategy.
Here is an example Lambda function to deal with a failed PutItem
request. The business
logic looks at who made the call. If it was made by jeffTheAdmin
, it retries the request,
updating the version
and expectedVersion
from the item currently in DynamoDB.
Otherwise, it rejects the mutation.
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) };