...on LocationReading { lat
long } }}
We have successfully demonstrated the use of DynamoDB batch operations using AWS AppSync.
Error Handling
In AWS AppSync, data source operations can sometimes return partial results. Partial results is the term we will use to denote when the output of an operation is comprised of some data and an error. Because error handling is inherently application specific, AWS AppSync gives you the opportunity to handle errors in the response mapping template. The resolver invocation error, if present, is available from the context as $ctx.error. Invocation errors always include a message and a type, accessible as properties
$ctx.error.message and $ctx.error.type. During the response mapping template invocation, you can handle partial results in three ways:
1. swallow the invocation error by just returning data
2. raise an error (using $util.error(...)) by stopping the response mapping template evaluation, which won’t return any data.
3. append an error (using $util.appendError(...)) and also return data
Let’s demonstrate each of the three points above with DynamoDB batch operations!
DynamoDB Batch operations
With DynamoDB batch operations, it is possible that a batch partially completes. That is, it is possible that some of the requested items or keys are left unprocessed. If AWS AppSync is unable to complete a batch, unprocessed items and an invocation error will be set on the context.
Error Handling
We will implement error handling using the Query.getReadings field configuration from the BatchGetItem operation from the previous section of this tutorial. This time, let’s pretend that while executing the Query.getReadings field, the temperatureReadings DynamoDB table ran out of provisioned throughput. DynamoDB raised a ProvisionedThroughputExceededException at the second attempt by AWS AppSync to process the remaining elements in the batch.
The following JSON represents the serialized context after the DynamoDB batch invocation but before the response mapping template was evaluated.
{ "arguments": { "sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00"
}, "source": null, "result": { "data": {
"temperatureReadings": [ null
],
"locationReadings": [ {
"lat": 47.615063, "long": -122.333551, "sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00"
} ] },
"unprocessedKeys": { "temperatureReadings": [ {
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00"
} ],
"locationReadings": []
} }, "error": {
"type": "DynamoDB:ProvisionedThroughputExceededException",
"message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
}, "outErrors": []
}
A few things to note on the context:
• the invocation error has been set on the context at $ctx.error by AWS AppSync, and the error type has been set to DynamoDB:ProvisionedThroughputExceededException.
• results are mapped per table under $ctx.result.data, even though an error is present
• keys that were left unprocessed are available at $ctx.result.data.unprocessedKeys.
Here, AWS AppSync was unable to retrieve the item with key (sensorId:1,
timestamp:2018-02-01T17:21:05.000+08:00) because of insufficient table throughput.
Note: For BatchPutItem, it is $ctx.result.data.unprocessedItems. For BatchDeleteItem, it is
$ctx.result.data.unprocessedKeys.
Let’s handle this error in three different ways.
Error Handling
1. Swallowing the invocation error
Returning data without handling the invocation error effectively swallows the error, making the result for the given GraphQL field always successful.
The response mapping template we write is familiar and only focuses on the result data.
Response mapping template:
$util.toJson($ctx.result.data)
GraphQL response:
{
"data": {
"getReadings": [ {
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00", "lat": 47.615063,
"long": -122.333551 },
{
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00", "value": 85.5
} ] } }
No errors will be added to the error response as only data was acted on.
2. Raising an error to abort the template execution
When partial failures should be treated as complete failures from the client’s perspective, you can abort the template execution to prevent returning data. The $util.error(...) utility method achieves exactly this behavior.
Response mapping template:
## there was an error let's mark the entire field
## as failed and do not return any data back in the response
#if ($ctx.error)
$util.error($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end
$util.toJson($ctx.result.data)
GraphQL response:
{
"data": {
"getReadings": null }, "errors": [
{
"path": [ "getReadings"
Error Handling
],
"data": null,
"errorType": "DynamoDB:ProvisionedThroughputExceededException", "errorInfo": {
"temperatureReadings": [ {
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00"
} ],
"locationReadings": []
},
"locations": [ {
"line": 58, "column": 3 }
],
"message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
} ]}
Even though some results might have been returned from the DynamoDB batch operation, we chose to raise an error such that the getReadings GraphQL field is null and the error has been added to the GraphQL response errors block.
3. Appending an error to return both data and errors
In certain cases, to provide a better user experience, applications can return partial results and notify their clients of the unprocessed items. The clients can decide to either implement a retry or translate the error back to the end user. The $util.appendError(...) is the utility method that enables this behavior by letting the application designer append errors on the context without interfering with the evaluation of the template. After evaluating the template, AWS AppSync will process any context errors by appending them to the errors block of the GraphQL response.
Response mapping template:
#if ($ctx.error)
## pass the unprocessed keys back to the caller via the `errorInfo` field $util.appendError($ctx.error.message, $ctx.error.type, null,
$ctx.result.data.unprocessedKeys)
#end
$util.toJson($ctx.result.data)
We forwarded both the invocation error and unprocessedKeys element inside the errors block of the GraphQL response. The getReadings field also return partial data from the locationReadings table as you can see in the response below.
GraphQL response:
{
"data": {
"getReadings": [ null,
{
"sensorId": "1",
"timestamp": "2018-02-01T17:21:05.000+08:00", "value": 85.5