TL;DR

To make an AppSync DynamoDB resolver throw exceptions on conditional check errors, we need to check $context.error in the response mapping template ourselves. Like this:

#if ( $ctx.error ) #if ( $ctx.error.type.equals("DynamoDB:ConditionalCheckFailedException") ) $util.error("your error message") #else $util.error($ctx.error.message, $ctx.error.type) #end
#end
$utils.toJson($context.result)

And now, the longer version.

The problem

AppSync lets us perform DynamoDB operations (GetItem, PutItem, UpdateItem etc.) without having to write any custom Lambda functions.

For example, to perform PutItem against a DynamoDB table, we need a request template like this:

{ "version" : "2018-05-29", "operation" : "PutItem", "key": { "name" : $utils.dynamodb.toDynamoDBJson($context.arguments.name) }, "attributeValues" : { "imageUrl" : $utils.dynamodb.toDynamoDBJson(...), "displayName" : $utils.dynamodb.toDynamoDBJson(...) }
}

Often, we want to have separate add and update mutations rather than just a single put mutation. For instance, in a social network app I’m building for a client we have a pre-configured list of sports users can do together.

We want to track the no. of users who declare their interest in doing a sport with others. So we added a count attribute to each sport.

img 5e975852ccb2a

Every time a user updates his/her sports preference, we’ll process the change through the DynamoDB Stream from the User table. The OnProfileUpdate function calculates the deltas and updates the count attribute in the aforementioned Sport table.

img 5e975861a8fb0

Through our CMS, admins can add new sports or change the displayName and/or imageUrl to show for each sport. But we don’t want to override the count attribute, which is updated by this OnProfileUpdate function.

So we need two mutations:

  • addSport : adds a new row to the Sport table using PutItem
  • updateSport : updates an existing row in the Sport table using UpdateItem to update just the displayName and imageUrl attribtues.

In both cases, we need to use condition expressions to ensure the sport doesn’t exist (for addSport) or it exists already (for updateSport). And we want to throw an appropriate error if the conditional check fails.

And that’s where the problem is – even when the conditional checks fail, the DynamoDB resolver did not return an error.

Given the following template for the addSport mutation:

img 5e97587670dd9

The following addSport request should have resulted in an error, at least according to the documented behaviour. The displayName we tried to write was different from the existing data.

img 5e97588627045

Similarly, an updateSport mutation with a non-existent sport should have thrown an error. But instead, it simply returned null.

img 5e97589834b04

What’s going on here?

Since the official examples are still referencing template version 2017–02–28 (which is no longer supported), I suspect the behaviour has changed in template version 2018–05–29.

For both PutItem and UpdateItem, when there is a conditional check error the resolver appears to fetch the current item and save it in the $context.result. This happens regardless if the item is different.

This is neither the behaviour I intuitively expect to see nor the behaviour that is current documented. I have raised this documentation issue through the Feedback channel on the documentation page. Hopefully, it’ll be addressed in the near future. In the meantime, if you run into this problem as I did then I hope this post helps you in some way.

The solution

When the DynamoDB resolver catches a ConditionalCheckFailedException, it also stores the error in $context.error.

In the response mapping template, you can inspect this attribute yourself and either rethrow the error or throw a different error altogether.

img 5e9758b02f67c

With this simple change, I was able to get the desired behaviour for both addSport and updateSport mutations.

img 5e9758c1e9e86

img 5e9758ca74463

This solution is based on the example from the official changelog, but there’s a typo in the example there. I have reported this typo so hopefully, it’ll be fixed soon. I have verified the snippet I shared above, so use that for now.

img 5e9758dd6e1ec

I have had a lot of fun working with AppSync this past few weeks and I will share more of my experience with you soon. Watch this space.

Until next time!

Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.

Screenshot 2019 10 19 at 11.44.09

img 5dab0c477fc72

Hi, my name is Yan Cui. I’m an AWS Serverless Hero and the author of Production-Ready Serverless. I specialise in rapidly transitioning teams to serverless and building production-ready services on AWS.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

You can contact me via Email, Twitter and LinkedIn.

Hire me.

The post AppSync: how to error on DynamoDB conditional check failures appeared first on theburningmonk.com.

vHSE8BYKmwQ