Solving real-life problems using serverless technologies

We explore a use-case of an everyday problem and create a simple solution using a serverless service we create.
Yev Krupetsky

August 23 2020 · 6 min read

SHARE ON

Solving real-life problems using serverless technologies

Background story

One of our team members ran into an interesting real-life problem. His husband wanted to sign up for a Yoga class that was filling up very quickly. The problem was that the Yoga school was updating the class availability on the website and not sending email notifications.

So as one does, he used Altostra to create a scheduled application that scrapes the Yoga website every hour, diffs the current classes schedule against the previously stored one in a DynamoDB table and publishes a message to an SNS topic if any changes are detected. He then subscribed a phone number to the SNS topic, and sure enough, everyone was happy.

When they were done, removing the application was a matter of a few clicks—with the peace of mind that they have it available to be deployed again in a few clicks if the need arises.

That's what we call Serverless Romance!

This is just one example of how you can use serverless services to quickly build a solution for real-world problems, and with Altostra, it's super easy.

The problem

In this post, we'll create a similar application to the one described above. To keep it in context with current real-life problems, our application will periodically poll a COVID-19 data API for a country of our choice and notify us if the infection count has gone up over a specific limit since the last check. Essentially, we're building a personal COVID-19 alerting service.

Setup

Our cloud service provider is going to be AWS. The runtime for our code is Node.JS 12 and the design and deployment will be done using Altostra.

The cloud resources we'll be using are:

  • Scheduler (an EventBridge rule with a schedule expression)
  • Lambda Function
  • DynamoDB Table
  • SNS Topic

We begin by initializing a project in a new directory:

$ mkdir covid19-tracker
$ cd covid19-tracker

$ alto init
✔ Initializing project

$ npm init -y; echo node_modules > .gitignore

$ git init
$ git add -A .
$ git commit -m "Initial"

Note the alto init command, it creates a new Altostra project for us.

Architecture

The next step is to design our application's architecture. We do that in Visual Studio Code with the help of the Altostra Extension.

Open the project in VSCode:

$ code .
Empty Altostra Project

We start by adding a Scheduler resource, a Lambda Function resource, a DynamoDB Simple Table resource and an SNS Topic resource. The Lambda Function will handle the scheduled event and host the business logic for our application—API polling, data persistence and alerting:

Created Resources

Next, we connect the resources. The Scheduler triggers the Lambda Function; the Lambda Function needs write-only (publish) access to the SNS Topic and read-write access to the DynamoDB table:

Connected Resources

Configuration

Next, we need to configure the resources. Most of the configuration is already set with default values, all we need to do is tweak some settings to fit the current project.

1. Set the Scheduler to run once a day

Scheduler Configuration

2. Limit the concurrency of the Lambda Function

By limiting the concurrency, we prevent potentially unwanted scaling since we don't expect to have more than 1 instance running:

Function Configuration

3. Add environment variables to the Lambda Function

We use these environment variables inside the Lambda Function to control our application's behavior:

Function Variables Configuration

We will see how to use parameters to populate environment variables for more advanced use-cases in a future post.

This is the only configuration that we need to modify for our application right now. And now that we have the architecture, it's time to fill in the code.

Code

The code for the Lambda Function is pretty simple, it:

  1. Gets the latest COVID-19 data from the API.
  2. Parses the API response.
  3. Gets the previously stored data from the table.
  4. Publishes a message to the SNS Topic if the data diff is greater than the threshold.
  5. Stores the new data in the table.

In the code below, note that we use the environment variables that we have provided earlier in the design and two additional variables that are injected implicitly whenever we connect resources to the Lambda Function. The TOPIC_NOTIFICATIONS01 and TABLE_COVIDDATA01 variables are the table and topic resources—you can see these names in the Lambda Function's settings:

Function Implicit Variables

And here is the code for the function:

const Axios = require('axios')
const AWS = require('aws-sdk')
const docClient = new AWS.DynamoDB.DocumentClient()
const sns = new AWS.SNS({ apiVersion: '2010-03-31' })

const {
  API_URL,
  COUNTRY,
  NOTIFY_WHEN_OVER,
  TOPIC_NOTIFICATIONS01,
  TABLE_COVIDDATA01,
} = process.env

if (!API_URL || !COUNTRY || !NOTIFY_WHEN_OVER || !TOPIC_NOTIFICATIONS01 || !TABLE_COVIDDATA01) {
  throw new Error(`Missing one of the expected environment variables.`)
}

exports.handler = async () => {
  try {
    const response = await Axios.get(API_URL)

    // Response validation was removed for brevity - it appears in the full source code on GitHub

    const infected = response.data.country.infected
    const previouslyInfected = await getPreviouslyInfected()
    const infectionDifference = infected - previouslyInfected

    if (infectionDifference > NOTIFY_WHEN_OVER) {
      console.info(`The infection difference [${infectionDifference}] is higher than the limit: ${NOTIFY_WHEN_OVER}.`)
      console.info(`Notifying subscribers.`)
      await notifySubscribers(infectionDifference)
    } else {
      console.info(`The infection difference [${infectionDifference}] is lower than the limit: ${NOTIFY_WHEN_OVER}.`)
    }

    await updatePreviouslyInfected(infected)
  } catch (err) {
    console.error(`Operation failed.`, err)
  }
}

async function updatePreviouslyInfected(infections) {
  const params = {
    TableName: TABLE_COVIDDATA01,
    Item: {
      'pk': 'previouslyInfected',
      infections,
    }
  }

  try {
    await docClient.put(params).promise()
  } catch (err) {
    console.error(`Unable to update table data:`, err)
    throw new Error(`Failed to store infections data.`)
  }
}

async function getPreviouslyInfected() {
  const params = {
    TableName: TABLE_COVIDDATA01,
    Key: {
      'pk': 'previouslyInfected'
    }
  }

  try {
    const data = await docClient.get(params).promise()
    return data && data.Item && data.Item.infections || 0
  } catch (err) {
    console.error(`Unable to get table data:`, err)
    throw new Error(`Failed to load infections data.`)
  }
}

async function notifySubscribers(infections) {
  const params = {
    Message: `WARNING! The infections difference has crossed the set limit of ${NOTIFY_WHEN_OVER}, it is now at ${infections}.`,
    TopicArn: TOPIC_NOTIFICATIONS01
  }

  try {
    await sns.publish(params).promise()
  } catch (err) {
    console.error(`Failed to publish message to the SNS topic.`, err)
    throw new Error(`Failed to notify subscribers.`)
  }
}

Deploy

Next, we use the Altostra CLI to deploy the project. In future posts, we'll go into more details about the available commands and how each of them is used.

For now, we need only two:

$ alto push v1.0
✔ Validating tag name 'v1.0'
✔ Checking user credentials
✔ Loading project
✔ Validating project file integrity
✔ Loading project
✔ Getting build repository
✔ Updating project file
✔ Validating image availability
✔ Packing files
✔ Getting repository information
✔ Uploading project parts

And:

$ alto deploy main:v1.0 --new Demo
Checking user credentials
✔ Processing request
✔ Deploying project

Deployment status of deployment main:

┌─────────┬─────────────────┬─────────────────────────┬────────┬─────────────┐
│ (index) │      User       │          Date           │ Image  │   Status    │
├─────────┼─────────────────┼─────────────────────────┼────────┼─────────────┤
│    0    │ 'Yev Krupetsky''8/22/2020, 3:45:50 PM''v1.0''Deploying' │
└─────────┴─────────────────┴─────────────────────────┴────────┴─────────────┘

Test

After the deployment is complete, the scheduled event is fired immediately and then every 1 day. And since we begin with no previous data in the table, an alert should arrive, indicating the whole number from the API as the diff. To test, I have subscribed my phone number to the SNS Topic, here's the result:

SMS Alert

Conclusion

In this post, we saw how serverless technologies can help us solve everyday problems. Numerous other everyday problems can be solved in similar ways. We can also compose such small solutions to create bigger solutions—maybe even throw in a custom dashboard website.

You can find the complete source code and the project files here.

I have intentionally skipped some of the details to keep the post short and to inspire you to use serverless technologies more. In future posts, we'll dive deeper into more complex solutions, and how Altostra's customers use Altostra to build their own.

Stay tuned...

By submitting this form, you are accepting our Terms of Service and our Privacy Policy

Thanks for subscribing!

Ready to Get Started?

Request a Demo

Copyright © 2020 Altostra. All rights reserved.