Bringing realtime to serverless web applications

By James Beswick

There are many ways to get results back to the front-end, but the real-time option is easiest with AWS IoT.

In web applications there is usually a front-end communicating with REST APIs to get work done in the back-end. Generally the API returns a result, indicating success or some value you are retrieving from the system.

But there is a tricky class of calls in serverless infrastructure where the return value ‘disappears’ or it’s difficult to push back to the front-end, either because it takes too long or — due to the chained nature of events — the originating caller no longer has access to the final result.

Take a look at this example:

In this diagram, some front-end code calls API gateway, resulting in an entry in DynamoDB, which generates a stream, triggering a Lambda function. But the return value from that last Lambda function has nowhere to go. So if there’s an error, problem or something we need the originating front-end code to know about, we’re a little stuck.

In fact, any time you attach functions to events generated from S3, DynamoDB or any number of other AWS services, the return value cannot be pushed back up the chain. For example, if you use Rekognition to analyze a photo dropped into an S3 Bucket, the response needs to be sent somewhere other than in the function’s return value.

There are several solutions to these problems, each with their own pros and cons depending on your use-case.

In the simplest case where you have a function that takes a while, you can wait. You are limited by the 30-second timeout at API Gateway so for long-lived processes, this isn’t going to work. It’s also not great from a cost perspective, since you are paying for waiting. But for simple, ad-hoc, low-usage applications, this is the easiest solution.

Example: once a month, the front-end requests a large object to be copied from one S3 bucket to another region, and waits for 3–5 seconds until S3 confirms this completes successfully. In this case, it’s probably not worth the effort to engineer anything more complex.

This is for cases when the back-end process takes longer than just a few seconds, or the length of the execution might exceed the 30-second timeout on API Gateway.

Here the front end makes a request of the API, and two things happen:

  • The called function immediately responds with an acknowledgement that the request has been received successfully (but not the result of the work). This response might include an ID for the front-end to reference later (if an ID was not generated by the front-end in the first place).
  • Next, there is a second API used to check the status of the work, and the front-end will poll periodically until the work is reported as complete, and the function returns the response. In AWS, this status function will typically check a DynamoDB table with the ID where the state of the request is managed.

This approach is easy to implement but generates many unnecessary requests and isn’t quite real-time. For example, if a transaction takes 20 seconds and the front-end polls every second, it takes 20-21 polling requests before the front-end receives the finished response.

In serverless, this may still be preferable from a cost perspective — 20 x 100ms requests cost less than waiting for 20 seconds but there’s an overhead, and it’s the code equivalent of having kids in the back of the car asking repeatedly “Are we there yet?”.

While IoT conjures images of hardware devices, it can also be used for serverless web apps. IoT uses a lightweight messaging protocol called MQTT which is a simple and efficient way to push messages around.

The publish-subscribe approach is often reserved for real-time applications but can work well to solve this problem. The benefits are that you don’t need to write a polling function or make multiple necessary requests, and you are not restricted by the API Gateway’s 30-second timeout. Better still, whether the process takes 5 seconds or 5 minutes, the front-end will immediately know when the task is finished since communication is real-time.

This is a great solution for front-ends such as dashboards, where the web page is loaded once but expected to stay in sync with changes in back-end data. But it’s also effective for any time-critical front-end application where you are looking to minimize wait times.

Under the covers, publish-subscribe is complex and it’s a chatty process with regular keep-alive pings to ensure the parties are still there. Fortunately, the AWS IoT device SDK abstracts this all away and makes it easy for us to build a front-end that is listening to messages generated by some far-off process in the back-end.

From a cost perspective, AWS charges you by connection minutes and the quantity of messages (there is also a generous Free Tier allowance). A single client connected for a whole year will cost 4.2 cents (excluding data charges) so while the dollars matter if you have thousands of users, it’s still relatively inexpensive.

For a simple example, I built the absolute minimum needed to get messages back to a browser front-end. I chose VueJS since it’s trivial to update the display, and a single-page application framework is a good use-case if you’re planning on using this sort of solution.

First, clone my repo from https://gitlab.com/jbesw/askjames-iot-demo. This is a VueJs application consisting of many files but we only care about these two:

  • components/IoT.vue: this contains all the logic, and is based upon boilerplate code from the IoT Core developer website.
  • App.vue: the main webpage which is listening for new messages and dropping the contents into an <h1> tag.

Before you can make this do anything meaningful, you will need to do two things. First, go to AWS Cognito and click ‘Create a User Pool’ — provide a name, click ‘Review defaults’ and then ‘Create Pool’. The following screen will show a Pool ID, which you need to copy for later:

Next, go to the AWS IoT console. Click ‘Manage’ (the ‘Things’ sub-category is auto-selected), click ‘Create’ and then ‘Create a single thing’. Provide a name, click ‘Next’, then ‘Create certificate’ (next to the ‘One-click certificate creation’ option). Click through the defaults until you see your new https endpoint and copy this down for later:

Back in the repo, open IoT.vue and enter your pool ID and host (endpoint) into the AWSConfiguration:

Type npm run dev in the console and visit http://localhost:8080 to fire up your application:

Now back in the IoT console, click on the “Test” option in the menu on the left. Select “Publish”, enter “askJames” as the topic and then enter a message in the JSON below. When you click “Publish to Topic”, the message will appear in the browser window instantaneously:

What just happened? We created an IoT “Thing” that we can both publish to and subscribe from. In practice, you would publish from Lambda functions throughout your application, using code like the sample shown in the backend folder in the repo:

The interest part is that the front-end application connects to IoT infrastructure using the SDK and waits for messages. It’s smart enough to survive disconnects and being left open for multiple days. It can also handle massive fan out operations, so if you have tens of thousands of users (or more), it will scale without issues. Also, in production systems, you can use Cognito to ensure that subscribers are only listening to topics where they are authorized.

Back to our original problem, if the operation ID is passed back through the IoT infrastructure as part of the message payload, the front-end can filter for this and detect when an operation is complete. Overall it’s a simple, elegant way to solve pushing responses all the way back to the front-end application.