Switching between async vs. sync invocation in AWS APIGW + AWS Lambda using JSON properties

AWS Lambda functions can be executed synchronously (RequestResponse mode) or asynchronously (Event mode), and AWS API Gateway Lambda integrations support both modes.

Async mode is incredibly valuable when you have a Lambda that’s well-built and reliable, yet takes a bit too long to finish. For example, we built a custom Zoom-Marketo integration that makes up to 8 different API calls during 1 Lambda invocation (and the calls can’t be run in parallel). Worst case scenario, it takes 8-10 seconds to finish, which isn’t something we want the end user to experience.

Amazon explicitly documents two ways to switch to async mode:

  1. Have the far client include the X-Amz-Invocation-Type HTTP header and pass that through to the Lambda. In this case the client has complete control.
  2. Hard-code the X-Amz-Invocation-Type HTTP header in the API Gateway definition, so the client can’t control it. (You could still define different resources, like /run/async and /run/sync.)

But I wanted a third way: the client chooses sync or async, but via the JSON payload, rather than an HTTP header. This way we can strictly validate the request against our JSON Schema, which would be impossible if you used the header. (Headers can’t be validated as being in a range of values.)

We already have a meta section of the request, so we’ll add async there as a Boolean:

   …
   "meta" : {
      "clientRequestId" : "a6149131-3c97-4f89-9d52-2e690ff67711",
      "async" : true
   }
   … 

Validate it in the JSON Schema model:

   … 
   "properties" : {
      "meta" : {
         "type" : "object",
         "required" : [ "clientRequestId" ],
         "additionalProperties" : false,
         "properties" : {
            "async" : {
               "type" : [ "boolean" ]
            },
            "clientRequestId" : {
               "type" : [ "string" ],
               "format" : "guid",
               "description" : "examples: 2d683f76-f769-4250-ae92-ee96a94977a6"
            }
         }
      }
   }
   … 

Then we need a tiny Response Mapping template. Drawing on the knowledge of Velocity Template Language I’ve gained via Marketo:

#set( $meta = $input.path("$.meta") )
#if( $meta.containsKey("async") && $meta["async"].equals(true) )
#set( $context.requestOverride.header["X-Amz-Invocation-Type"] = "Event" )
#end
${input.json("$")}

Screenshot of AWS Console for context:

That’s it!