Al3x' Tech Blog

An AWS Lambda execution report webpage built with Lambda Destinations, CDK and Svelte

27 Feb 2020

Until recently the only built-in way to programmatically react to AWS Lambda asynchronous execution (failures) was the dead letter queue feature. This was suboptimal to say little because for example it didn’t give developers any way to programmatically act upon Lambda successes leaving us with only a few convoluted options like parsing CloudWatch Logs events.

To fill that gap AWS recently announced Lambda Destinations. Quoting from the announcement post:

With Destinations, you can route asynchronous [Lambda] function results as an execution record to a destination resource without writing additional code. An execution record contains details about the request and response in JSON format including version, timestamp, request context, request payload, response context, and response payload. For each execution status such as Success or Failure you can choose one of four destinations: another Lambda function, SNS, SQS, or EventBridge. Lambda can also be configured to route different execution results to different destinations.

At first sight it might not sound like a big deal but to me it represents a truly interesting feature that opens up a wide new range of possibilities.

TL;DL

For the ones of you who’re in a hurry, you can inspect the final result here and the required code changes here.

Feature Request

As announced in a previous post about CDK and the publish-to-social stack, I had in mind some improvements for the project before considering it feature complete and tag it as v1.0.0.

One of the missing pieces was an easy way to visualize the actual successful execution of each publishing Lambda, anything handier than opening the AWS web console in search of CloudWatch Logs entries.

I already heard about Lambda Destinations but I haven’t had the chance yet to actually make use of them so this case sounded like the perfect match to try out the new feature.

Implementation

Backend

If we ignore the automatically generated Lambda CloudWatch Logs which are meant more for debugging and troubleshooting, some kind of storage mechanism to persist the publishing events was missing in the initial publish-to-social version so, after spending a while considering what might be the best option in this case, I settled for a single CloudWatch LogGroup as container for <social-name> LogStreams, one for each Lambda publisher (twitter, mastodon, etc…).

Another option I considered was a DynamoDB table but I ruled that out for a few reasons, mostly because it’s arguably overkill for this use case (e.g. I have zero need for high scalability nor making complex queries nor of any other powerful feature that Dynamo provides).

To glue all the parts together these addition were needed:

  • a single new CloudWatch LogGroup (line 55) and a new Stream for each publisher (line 80)
  • a new Lambda that generates log entries (with metadata like url, title, timestamp, etc.) into the CW Log Streams mentioned above to be used as destination by all the Lambda publishers
  • a new route (GET /social_report, line 70) in the api Lambda that reads the new CloudWatch Logs data and returns it to the web client as JSON response

The (slightly simplified) final backend architecture looks like this:

Architecture Diagram

Frontend

For the frontend side of things I decided to just add a new page to this very Jekyll blog and have it publicly available at this URL.

As a side note, I already have plans to migrate the blog from Jekyll to Hugo so I didn’t invest too much on the page layout design at this time.

For the dynamic part I could have added some good old jQuery given that it’s already been used for the contact page… but why on Earth should I do that when there are so many new fancy web frameworks out there to be tried out? 😀

In the last years I’ve been following the Vue.js project and I still consider it a very good choice for developing SPA and the like but since a while Svelte keeps constantly popping up in my news feeds. This simple use case felt like the perfect chance to get started and see how Svelte differs from Vue (and others) approach.

Actually I must say I’m quite happy with the choice: the examples from the official documentation get you up to speed very fast so I was able to quickly add the needed simple functionality with just around 70 lines of code (including Javascript and markup template).

This is the main .svelte file content:

<script>
    let done    = false
    let error   = null
    let socials = {}

    const URL     = "https://api.l3x.in/social_report"
    const request = new Request(URL)

    function hideSpinner() { document.querySelector("#spinner").style.display = "none" }

    fetch(request).then(function (response) {
        if (!response.ok) {
            error = response.status
            hideSpinner()

        } else {
            response.json().then(function (body) {
                const message = body["message"]
                socials = Object.keys(message)
                                .map(social => [social, message[social]
                                .map(entry => JSON.parse(entry["message"]))])
                done = true
                hideSpinner()
            })
        }
    })
</script>

{#if error }
<h1 class="alert alert-danger text-center" role="alert">Error { error }</h1>

{:else if done }
<h1 class="text-center">Publish-to-Social Events</h1>
    <ul class="p-0">
    {#each socials as [social, entries]}
        <li class="mt-3 list-unstyled">
            <h2>{ social }</h2>
            {#if entries}
            <ul class="p-0">
                {#each entries as entry}
                <li class="card p-0">
                    <div class="card-body">
                        <p class="card-title">
                            <em>{ entry.timestamp }</em>
                        </p>

                        <p>
                            <strong>{ entry.title }</strong>
                        </p>

                        <p>
                            <a
                                href={ entry.url }
                                alt={ entry.title }
                            >
                                { entry.url }
                            </a>
                        </p>
                    </div>
                </li>
                {/each}
            </ul>
            {:else}
            <div class="card">
                <span class="card-body">
                    <h2 class="text-center">No Entries</h2>
                </span>
            </div>
            {/if}
        </li>
    {/each}
    </ul>
{/if}

Conclusion

AWS Lambda Destinations are truly powerful and easy to introduce to your existing Lambda-based application which means the temptation to use them to hammer out any nail you find is strong. My recommendation is to resist that temptation and use them only after analyzing your context and deciding if it truly makes sense in your scenario.

The one use case I showed in this article is one example where I believe they are the right tool for the job and I can already think of some more (sending notifications, generating analytics data, etc), but for more complex workflows you might also want to consider AWS Step Functions for example.

In general working with AWS CDK is a real pleasure and this time was no different. The code changes required to add the functionality were minimal and all quite straightforward (most notable the addition of a on_success parameter to the Lambda builder utility function at line 61).

Finally, Svelte is a very interesting project that’s quickly gaining traction and adoption. Even though I just scratched the surface of what’s possible with Svelte I already got the feeling that it might be a very good fit at least for developing simple web applications, while for something more complex so far I still believe Vue.js (combined to Vuex and vue-router and all the available plugins and community resources) might be a better choice. That said, I don’t know Svelte well enough yet to make a final call so for now I’ll keep both frameworks ready and warm in my toolbox.

As usual I encourage you to leave some comments here below or contact me directly, I’m eager to know what YOU think!

* hashed with MD5, i.e. never shown nor stored in plain text