Building serverless APIs using AWS services


engels

Lately I’ve been talking on a few occasions to peers and customers about how to build serverless Web APIs using AWS. The key objective of this talk is to discuss different kinds of communication patterns and how we can implement these using AWS services. Since then I have received several requests to share the slides, but since I think these slides do not stand well on their own I decided to go for this piece of writing – illustrated with the slides.

An API in its simplest form is the one where requests and responses appear in the same roundtrip. A client makes a request and the client is blocked to await the response. This way of communication, also known as “synchronous request/reply”, is conveniently easy to implement on AWS with Lambda fronted by API Gateway, see this example.

In many cases this type of blocking interaction is inevitable, for example when the client requires and immediate response to continue. But synchronous APIs have limits that we run into more quickly with todays distributed system architectures. Therefore, in many cases, going for the asynchronous alternative is a compelling option. Ever more solutions are being designed to be more resilient and removing the ‘temporal coupling’ between services is an important step forward.

To introduce asynchronicity, we add some middleware to decouple the request from the (optional) response. On AWS there are many choices, including SNS, SQS and Kinesis. Exactly which middleware to choose depends on many things that deserve a blog post of its own. The first question we should ask ourselves, though, is whether we can handle a push-based solution (using SNS or Kinesis) where we cannot amortise the traffic in any way – just like with “synchronous request/reply”), or if we require a pull-based solution (using SQS or Kinesis) where we have the ability of buffering spikes in traffic and work our way through at our desired pace.

The need for a request handler Lambda

When decided on a middleware solution, next up is a typical AWS engineering consideration: Do we need a separate “request handler” Lambda? 

As discussed in a previous post, when implementing an asynchronous API on AWS the first first solution that came to my mind when introducing middleware was to use API Gateway together with at least two Lambda’s; one to handle the request from API Gateway – let’s call it the “request handler” and a second “worker” function to consume from the middleware and, uhm, do the actual work.

It turns out having the separate Lambda functions is only necessary, if—and only if— we want to do more with the “message” (a representation of the request from API Gateway) than just passing it on to the middleware service. Otherwise, we can just use API Gateway to act as a ‘service proxy’ in front of SQS, SNS, etc. Unfortunately, it is not not very well documented and these API Gateway integration configurations are difficult to get right. See these previous posts about setting it up for SNS and SQS with CloudFormation.

Asynchronous Communication Patterns

With API Gateway, Lambda and messaging middleware of our choice we can implement some common asynchronous communication patterns.

If we want to ensure that our “request” has been delivered, but are not interested in the outcome, then we can go for the simplest form of asynchronous APIs – “fire-and-forget”.

If we are interested in the outcome, we can implement another endpoint the client can use to poll for the status of the result. Some storage service, such as DynamoDB, can be used to store the status. Note that in this case we need a separate “request handler”, because we need to do more with the incoming request than just passing it on to the middleware service. 

This is generally not as efficient as the alternative to polling:

Given we have a callback handler to process the result, the service provider calls the client back when done on the provided callback url. Simple yet effective.

An Example

The following example shows how we can use “async request/polling” to implement an image recognition service.

After a request is sent (1) the API Gateway invokes the request handler which stores an image in an S3 bucket, (2) writes the status to a DynamoDB table and (3) publishes an event to a SNS topic. After that, (4) the client receives a “202 Accepted” response to indicate the request has been accepted for processing, but the processing has not been completed. Next, the SNS topic notifies the subscribed Lambda acting as a “worker” which will call AWS Rekognition with a reference to the image stored in our S3 bucket. When this call returns it will write the result (comprised of labels describing the image) and updates the status in the DynamoDB table.

In the meantime, the status of the process can be obtained by the client by using the other endpoint that retrieves the status from DynamoDB, i.e. it can be polled.

More to come

During last week’s “AWS Re:Invent” Amazon announced the launch of WebSockets for API Gateway. This enables us to establish a WebSocket connection from (say) a browser, to API Gateway, which then invokes a Lambda function when a message is received. On the other hand, if we want to update the client application with new data from the API we should be able to make a request to a callback URL with the unique connection-id. Looking forward to experiment with Lambda having “live connections” to clients!