Showing posts with label graphical lambda composition. Show all posts
Showing posts with label graphical lambda composition. Show all posts

Monday, May 14, 2018

How to rob a bank: no servers - just a ballpoint pen!

Okay, let's face it: this article has nothing to do with robbery, banks or, heck, ballpoint pens; but it's a good attention grabber (hopefully!), thanks to Chef Horst of Gusteau's. (Apologies if that broke your heart!)

Rather, this is about getting your own gossip feed—sending you the latest and the hottest, within minutes they become public—with just an AWS account and a web browser!

Maybe not as exciting as a bank robbery, but still worth reading on—especially if you're a gossip fan and like to always have an edge over the rest of your buddies.

Kicking out the server

Going with the recent hype, we will be using serverless technologies for our mission. You guessed it, there's no server involved. (But, psst, there is one!)

Let's go with AWS, which offers an attractive Free Tier in addition to a myriad of rich serverless utilties: CloudWatch scheduled events to trigger our gossip seek, DynamoDB to store gossips and track changes, and SNS-based SMS to dispatch new gossips right into your mobile!

And the best part is: you will be doing everything—from defining entities and composing lambdas to building, packaging and deploying the whole set-up—right inside your own web browser, without ever having to open up a single tedious AWS console!

All of it made possible thanks to Sigma, the brand new truly serverless IDE from SLAppForge.

Sigma: Think Serverless!

The grocery list

First things first: sign up for a Sigma account, if you haven't already. All it takes is an email address, AWS account (comes with that cool free tier, if you're a new user!), GitHub account (also free) and a good web browser. We have a short-and-sweet writeup to get you started within minutes; and will probably come up with a nice video as well, pretty soon!

A project is born

Once you are in, create a new project (with a catchy name to impress your buddies—how about GossipHunter?). The Sigma editor will create a template lambda for you, and we can start right away.

GossipHunter at t = 0

Nurtured with <3 by NewsAPI

As my gossip source, I picked the Entertainment Weekly API by newsapi.org. Their API is quite simple and straightforward, and a free signup with just an email address gets you an API key with 1000 requests per day! In case you have your own love, feel free to switch just the API request part of the code (coming up soon!), and the rest should work just fine!

The recipe

Our lambda will be periodically pulling data from this API, comparing the results with what we already know (stored in DynamoDB) and sending out SMS notifications (via SNS) to your phone number (or email, or whatever other preferred medium that SNS offers) for any already unknown (hence "hot") results. We will store any newly seen topics in DynamoDB, so that we can prevent ourselves from sending out the same gossip repeatedly.

(By the way, if you have access to a gossip API that actually emits/notifies you of latest updates (e.g. via webhooks) rather than us having to poll for and filter them, you can use a different, more efficient approach such as configuring an API Gateway trigger and pointing the API webhook to the trigger endpoint.)

Okay, let's chop away!

The wake-up call(s)

First, let's drag a CloudWatch entry from the left Resources pane and configure it to fire our lambda; to prevent distractions during working hours, we will configure it to run every 15 minutes, only from 7 PM (when you are back from work) to midnight, and from 5AM to 8 AM (when you are on your way back to work). This can be easily achieved through a New, Schedule-type trigger that uses a cron expression such as 5-7,19-23 0/15 ? * MON-FRI *. (Simply paste 0/15 , 5-7,19-23 (no spaces) and MON-FRI into the Minutes, Hours and Day of Week fields, and type a ? under Day of Month.)

CloudWatch Events trigger: weekdays

But wait! The real value of gossip is certainly in the weekend! So let's add (drag, drop, configure) another trigger to run GossipHunter all day (5 AM - midnight!) over the weekend; just another cron with 0/10 (every ten minutes this time! we need to be quick!) in Minutes, 5-23 in Hours, ? in Day of Month and SAT,SUN in Day of Week.

CloudWatch Events trigger: weekends

Okay, time to start coding!

Grabbing the smoking hot stuff

Let's first fetch the latest gossips from the API. The requests module could do this for us in a heartbeat, so we'll go get it: click the Add Dependency button on the toolbar, type in requests and click Add once our subject appears in the list:

'Add Dependency' button

Now for the easy part:

  request.get(`https://newsapi.org/v2/top-headlines?sources=entertainment-weekly&apiKey=your-api-key`,
  (error, response, body) => {

    callback(null,'Successfully executed');
  })

Gotta hide some secrets?

Wait! The apiKey parameter: do I have to specify the value in the code? Since you probably would be saving all this in GitHub (yup, you guessed right!) won't that compromise my token?

We also had the same question; and that's exactly why, just a few weeks ago, we introduced the environment variables feature!

Go ahead, click the Environment Variables ((x)) button, and define a KEY variable (associated with our lambda) holding your API key. This value will be available for your lambda at runtime, but it will not be committed into your source; you can simply provide the value during your first deployment after opening the project. And so can any of your colleagues (with their own API keys, of course!) when they get jealous and want to try out their own copy of your GossipHunter!

Defining the 'KEY' environment variable

(Did I mention that your friends can simply grab your GossipHunter's GitHub repo URL—once you have saved your project—and open it in Sigma right away, and deploy it on their own AWS account? Oh yeah, it's that easy!)

Cool! Okay, back to business.

Before we forget it, let's append process.env.KEY to our NewsAPI URL:

  request.get(`https://newsapi.org/v2/top-headlines?sources=entertainment-weekly&apiKey=${process.env.KEY}`,

And extract out the gossips list, with a few sanity checks:

  (error, response, body) => {
    let result = JSON.parse(body);
    if (result.status !== "ok") {
      return callback('NewsAPI call failed!');
    }
    result.articles.forEach(article => {

    });

    callback(null,'Successfully executed');
  })

Sifting out the not-so-hot

Now the tricky part: we have to compare these with the most recent gossips that we have dispatched, to detect whether they are truly "new" ones, i.e. filter the ones that have not already been dispatched.

For starters, we shall maintain a DynamoDB table gossips to retain the gossips that we have dispatched, serving as our GossipHunter's "memory". Whenever a "new" gossip (i.e. one that is not already available in our table) is encountered, we shall send it out via SNS, the Simple Notification Service and add it to our table so that we will not send it out again. (Later on we can improve our "memory" to "forget" (delete) old entries so that it would not keep on growing indefinitely, but for the moment, let's not worry about it.)

What's that, Dynamo-DB?

For the DynamoDB table, simply drag a DynamoDB entry from the resources pane into the editor, right into the forEach callback. Sigma will show you a pop-up where you can define your table (without a round trip to the DynamoDB dashboard!) and the operation you intend to perform on it. Right now we need to query the table for the gossip in the current iteration, so we can zip it by

  • entering gossips into the Table Name field and url for the Partition Key,
  • selecting the Get Document operation, and
  • entering @{article.url} (note the familiar, ${}-like syntax?) in the Partition Key field.

Your brand new DynamoDB table 'gossips' with a 'Get Document' operation

      result.articles.forEach(article => {
        ddb.get({
          TableName: 'gossips',
          Key: { 'url': article.url }
        }, function (err, data) {
          if (err) {
            //handle error
          } else {
            //your logic goes here
          }
        });

      });

In the callback, let's check if DynamoDB found a match (ignoring any failed queries):

        }, function (err, data) {
          if (err) {
            console.log(`Failed to check for ${article.url}`, err);
          } else {
            if (data.Item) {  // match found, meaning we have already saved it
              console.log(`Gossip already dispatched: ${article.url}`);
            } else {

            }
          }
        });

Compose (160 characters remaining)

In the nested else block (when we cannot find a matching gossip), we prepare an SMS-friendly gossip text (including the title, and optionally the description and URL if we can stuff them in; remember the 160-character limit?). (Later you can tidy things up by throwing in a URL-shortener logic and so on, but for the sake of simplicity, I'll pass.)

            } else {
              let titleLen = article.title.length;
              let descrLen = article.description.length;
              let urlLen = article.url.length;

              let gossipText = article.title;
              if (gossipText.length + descrLen < 160) {
                gossipText += "\n" + article.description;
              }
              if (gossipText.length + urlLen < 160) {
                gossipText += "\n" + article.url;
              }

Hitting "Send"

Now we can send out our gossip as an SNS SMS. For this,

  • drag an SNS entry from the left pane into the editor, right after the last if block,
  • select Direct SMS as the Resource Type,
  • enter your mobile number into the Mobile Number field,
  • populate the SMS text field with @{gossipText},
  • type in GossipHuntr as the Sender ID (unfortunately the sender ID cannot be longer than 11 characters, but it doesn't really matter since it is just the text message sender's name; besides, GossipHuntr is more catchy, right? :)), and
  • click Inject.

But...

Wait! What would happen if your best buddy grabs your repo and deploys it; his gossips would also start flowing into your phone!

Perhaps a clever trick would be to extract out the phone number into another environment variable, so that you and your best buddy can pick your own numbers (and part ways, still as friends) at deployment time. So click the (x) again and add a new PHONE variable (with your phone number), and use it in the Mobile Number field instead as (you guessed it!) @{process.env.PHONE}:

Behold: gossip SMSs are on their way!

            } else {
              let titleLen = article.title.length;
              let descrLen = article.description.length;
              let urlLen = article.url.length;

              let gossipText = article.title;
              if (gossipText.length + descrLen < 160) {
                gossipText += "\n" + article.description;
              }
              if (gossipText.length + urlLen < 160) {
                gossipText += "\n" + article.url;
              }

              sns.publish({
                Message: gossipText,
                MessageAttributes: {
                  'AWS.SNS.SMS.SMSType': {
                    DataType: 'String',
                    StringValue: 'Promotional'
                  },
                  'AWS.SNS.SMS.SenderID': {
                    DataType: 'String',
                    StringValue: 'GossipHuntr'
                  },
                },
                PhoneNumber: process.env.PHONE
              }).promise()
                .then(data => {
                  // your code goes here
                })
                .catch(err => {
                  // error handling goes here
                });
            }

(In case you got overexcited and clicked Inject before reading the but... part, chill out! Dive right into the code, and change the PhoneNumber parameter under the sns.publish(...) call; ta da!)

Tick it off, and be done with it!

One last thing: for this whole contraption to work properly, we also need to save the "new" gossip in our table. Since you have already defined the table during the query operation, you can simply drag it from under the DynamoDB list on the resources pane (click the down arrow on the DynamoDB entry to see the table definition entry); drop it right under the SNS SDK call, select Put Document as the operation, and configure the new entry as url = ${article.url} (by clicking the Add button under Values and entering url as the key and @{article.url} as the value).

Dragging the existing DynamoDB table in; for our last mission

Adding a 'sent' marker for the 'hot' gossip that we just texted out

                .then(data => {
                  ddb.put({
                    TableName: 'gossips',
                    Item: { 'url': article.url }
                  }, function (err, data) {
                    if (err) {
                      console.log(`Failed to save marker for ${article.url}`, err);
                    } else {
                      console.log(`Saved marker for ${article.url}`);
                    }
                  });
                })
                .catch(err => {
                  console.log(`Failed to dispatch SMS for ${article.url}`, err);
                });

Time to polish it up!

Since we'd be committing this code to GitHub, let's clean it up a bit (all your buddies would see this, remember?) and throw in some comments:

let AWS = require('aws-sdk');
const sns = new AWS.SNS();
const ddb = new AWS.DynamoDB.DocumentClient();
let request = require('request');

exports.handler = function (event, context, callback) {

  // fetch the latest headlines
  request.get(`https://newsapi.org/v2/top-headlines?sources=entertainment-weekly&apiKey=${process.env.KEY}`,
    (error, response, body) => {

      // early exit on failure
      let result = JSON.parse(body);
      if (result.status !== "ok") {
        return callback('NewsAPI call failed!');
      }

      // check each article, processing if it hasn't been already
      result.articles.forEach(article => {
        ddb.get({
          TableName: 'gossips',
          Key: { 'url': article.url }
        }, function (err, data) {
          if (err) {
            console.log(`Failed to check for ${article.url}`, err);
          } else {
            if (data.Item) {  // we've seen this previously; ignore it
              console.log(`Gossip already dispatched: ${article.url}`);

            } else {
              let titleLen = article.title.length;
              let descrLen = article.description.length;
              let urlLen = article.url.length;

              // stuff as much content into the text as possible
              let gossipText = article.title;
              if (gossipText.length + descrLen < 160) {
                gossipText += "\n" + article.description;
              }
              if (gossipText.length + urlLen < 160) {
                gossipText += "\n" + article.url;
              }

              // send out the SMS
              sns.publish({
                Message: gossipText,
                MessageAttributes: {
                  'AWS.SNS.SMS.SMSType': {
                    DataType: 'String',
                    StringValue: 'Promotional'
                  },
                  'AWS.SNS.SMS.SenderID': {
                    DataType: 'String',
                    StringValue: 'GossipHuntr'
                  },
                },
                PhoneNumber: process.env.PHONE
              }).promise()
                .then(data => {
                  // save the URL so we won't send this out again
                  ddb.put({
                    TableName: 'gossips',
                    Item: { 'url': article.url }
                  }, function (err, data) {
                    if (err) {
                      console.log(`Failed to save marker for ${article.url}`, err);
                    } else {
                      console.log(`Saved marker for ${article.url}`);
                    }
                  });
                })
                .catch(err => {
                  console.log(`Failed to dispatch SMS for ${article.url}`, err);
                });
            }
          }
        });
      });

      // notify AWS that we're good (no need to track/notify errors at the moment)
      callback(null, 'Successfully executed');
    })
}

All done!

3, 2, 1, ignition!

Click Deploy on the toolbar, which will set a chain of actions in motion: first the project will be saved (committed to your own GitHub repo, with a commit message of your choosing), then built and packaged (fully automated!) and finally deployed into your AWS account (giving you a chance to review the deployment summary before it is executed).

deployment progress

Once the progress bar hits the end and the deployment status says CREATE_COMPLETE (or UPDATE_COMPLETE in case you missed a spot and had to redeploy), GossipHunter is ready for action!

Houston, we're GO!

Until your DynamoDB table is primed up (populated with enough gossips to start waiting for updates), you would receive a trail of gossip texts. After that, whenever a new gossip comes up, you will receive it on your mobile within a matter of minutes!

All thanks to the awesomeness of serverless and AWS, and Sigma that brings it all right into your web browser.

Friday, February 16, 2018

Sigma: The New Kid on the Serverless Block

Despite its young age (barely 73 years; in comparison to, say, automobiles (200+), digital computing is growing and flourishing rapidly; and so are the associated tools and utilities. Today's "hot" topic or tech is no longer hot tomorrow, "legacy" in a week and "deprecated" in a month.

Application deployment and orchestration is no exception: in just three decades we have gone from legacy monoliths to modular systems, P2P integration, middleware, SOA, microservices, and the latest, functions or FaaS. The deployment paradigm has shifted to comply, with in-house servers and data centers, enterprise networks, VMs, containers, and now, "serverless".

Keeping up with things was easy so far, but the serverless paradigm demands quite a shift in the developer mindset (not to mention the programming paradigm). This, combined with the lack of intuitive tooling, has fairly hindered the adoption of serverless application development, even among the cutting-edge developers.

And (you guessed it), that's where _____ comes into play.

Missing something?

Yup.

A way to glue stuff together.

A way to compose a serverless application care-free. Without having to worry—and to read tons of documentation, watch reels of tutorials, or trial-and-error till your head is on fire—about all the bells and whistles of the underlying framework and related services.

Essentially, a sum-up of all that is serverless.

Sum.

Sigma.

Σ.

Sigma Logo

What's in a name?

As the name implies, (quoted from the official website)

The Sigma editor is a hybrid between the simplicity of drag-and-drop style development,
and the full and unlimited power of raw code.

The drag-and-drop events generate sample or usage snippets to quickly get started,
and introduce a powerful, uniform and intuitive library with auto-completion,
which allow users to quickly become productive in developing Serverless applications
that integrate with a myriad of AWS based services.

Making of...

Before Sigma, a bit of background of its origins.

As a first-time user of AWS Lambda, one of our team members brought up an impressive series of questions: if serverless is so cool, why is it so complicated to get an application up and running in Lambda?

(His quest, converted into a presentation, is [right here].)

And we ourselves started trying out the same thing. Guess what, we got the same questions as well.

So we set out to devise something that could bypass all those tedious steps: something where we could just write our code, save it, and deploy it as a working serverless application, without having to wander from dashboard to dashboard, or sift through heaps of documentation or reels of video tutorials.

And we ended up with Sigma!

Yet another IDE?

At first glance, Sigma looks like another cloud IDE that additionally supports deploying an application directly into a serverless provider environment (AWS so far).

However, there are a few not-to-be-missed distinctions:

  • Unlike many of the existing cloud IDEs, Sigma itself is truly serverless; it runs completely inside your browser, using backend services only for user authentication and analytics, and requires no dedicated server/VM/container to be running in the background. Just fire up your browser, log in, and start coding your dream away.
  • Sigma directly interacts with and configures the serverless platform on your behalf, using the credentials that you provide, saving hours of configuration and troubleshooting time. No more back-and-forth between overcomplicated dashboards and dizzying configurations.
  • Sigma encapsulates the complexities of the serverless platform, such as service entities, access policies, invocation trigger configurations and associated permissions, and even some API invocation syntaxes, saving you the trouble of having to delve into piles of documentation.
  • All of this comes in a fairly simple, intuitive environment, with easy, drag-and-drop composition combined with the full power of written code. Drag and drop a DynamoDB table into the UI, pick your operation and just write your logic, and Sigma will do the magic of automatically creating, configuring and managing the DynamoDB table on your AWS account.

Now, I won't say that's "just another IDE"; what say you?

A serverless platform?

Based on the extent of its capabilities, you may also be inclined to classify Sigma as a serverless platform. This is true to a great extent; after all, Sigma facilitates all of it—composing, building and deploying the application! However...

Hybrid! It's a hybrid!

Yup, Sigma is a hybrid.

Fusion of a cloud IDE (which in itself is a hybrid of graphical composition and granular coding) and a serverless development framework (which automatically deploys and manages the resources, permissions, wiring and other bells and whistles of your serverless application).

One of a kind.

To be precise, the first of its kind.

A new beginning

With Sigma, we hope to redefine serverless development.

Yup. Seriously.

From here onwards, developers shall simply focus on what they need to achieve: workflow, business logic, algorithm, whatever.

Not about all the gears and crankshafts of the platform on which they would deploy the whole thing.

Not about the syntax of, or permissions required by, platform-specific API or service calls.

Not about the deployment, configurations and lifecycle of all the tables, buckets, streams, schedulers, REST endpoints, queues and so forth, that they want to use within their application.

Because Sigma will take care of it all.

And we believe our initiative would

  • make it easy for newcomers to get started with serverless development,
  • improve the productivity of devs that are already familiar with—or even experts of—serverless development,
  • speed up the adoption of serverless development among the not-yet-serverless community,
  • allow y'all to "think serverless", and
  • make serverless way much fun!

We have proof!

While developing Sigma, we also wanted to verify that we were doing the right thing, and doing it right. So we bestowed upon two of our fellows, the responsibility to develop two showcase applications using Sigma: a serverless accounting webapp, and a location-based mobile dating app.

To our great joy, both experiments were successful!

The accounting app SLAppBook is now live for public access. By default it runs against one of our test serverless backends, but you can always deploy the serverless backend project on your own AWS account via Sigma and point the frontend to your brand new backend, after which you can use it for your own personal use!

The dating app HotSpaces is currently undergoing some rad improvements (see, now it's the frontend that takes time to develop!) and will be out pretty soon!

So, once again, we have proof that Sigma really rocks it!

Far from perfection, but getting there; fast!

Needless to say, Sigma is pretty much an infant. It needs quite a lot more—more built-in services, better code suggestions, smarter resource handling, faster builds and deployments, support for other cloud platforms, you name it—before it can be considered "mature".

But we are getting there. And we will get there. Fast.

We will publish our roadmap pretty soon, which would include (among other things) adding more AWS services, supporting integration with external APIs/services and, most importantly, expanding to other cloud providers like GCP and MS Azure.

That's where we need your help.

We need you!

Needless to say, you are most welcome to try out Sigma. Sign up here, if you haven't already, and start playing around with our samples (once you are signed in to Sigma, you can directly open them via the projects page). Or, if you feel adventurous, start off with a clean slate, and start building your own serverless application.

We are continually smoothening the ride, but you may hit a few bumps here and there. Possibly even hard ones. Sometimes even impassable. Maybe none, if you are really lucky.

Either way, we are eagerly waiting for your feedback. Just write us about anything that came to your mind: a missing functionality, a popular AWS service that you really missed in Sigma (there are hundreds, no doubt!), the next cloud platform you would like Sigma to support; a failed build, a faulty deployment, a nasty error that hogged your browser; or even the slightest of improvements that you would like to see, like a misaligned button, a hard-to-scroll pop-up or a badly-named text label.

You can either use our official feedback form or the "Report an Issue" option on the IDE Help menu, post your feedback in our GitHub issue tracker, or send us a direct email at info@slappforge.com.

If you would like to join hands with us in our forward march, towards a "think serverless" future, drop us an email at info@slappforge.com right away.

Welcome to Sigma!

That's it; time to start your journey with Sigma!

(Originally authored on Medium.)