Your second serverless multi-tier web app on AWS

Slides:

Part 2/2

Introduction

This tutorial resumes where the first part in this series stopped.

To recap, I showed you how to get your first multi-tier architecture set up on AWS:

image alt text

I also showed how to use your own domain, how to script the process from start to finish.

All that while kissing server management goodbye :)

In this video, I want to cover what you’re likely going to want to do after doing all that.

Topics

Swagger as an API management tool

What is Swagger?

Swagger is a very popular open source framework for working with APIs.

It allows you to design your API using a web editor in language agnostic manner.

Why bother with Swagger?

You can generate documentation and even use it to generate boilerplate code for many frameworks on your behalf. It can generate client and server code for Node.JS, Spring MVC, C# and many others. It can even output a JMeter script, which is an open source tool that you can consider for your load testing needs.

Why bother with Swagger when working with API Gateway?

API Gateway supports integration for Swagger. That means we can import and export Swagger configuration in and out of API Gateway whether automatically with the SDKs or manually with the admin console. And frankly, it’s more fun to work with Swagger than the API Gateway admin console.

Last but not least, a reason to use Swagger is in case you want to have the option of hosting your API elsewhere. At any time you can generate a Swagger definition even if you never used Swagger in the API before.

Walkthrough

What the demo is about

In this demo, we’ll continue with the TodoMVC example that I started. But in this second part, I’m going to show you how to write the API from the ground up using Swagger. I’ll also cover how to use a Amazon’s CloudFront CDN to make the site load faster and I’ll talk about scripts to automate this process.

The lambda function and the DynamoDB table will remain the same as the one before, i.e the multi-tier function and the todo table:

image alt text

Designing the API

There are some differences compared with how I showed you when writing the API from API Gateway. For example, API Gateway allows you to write an "ANY" HTTP method. This is just a convenience in API Gateway that matches requests of “any” HTTP method. In Swagger, you’ll have to define a resource and method for each.

The Swagger editor

The Swagger project includes a web-based editor that you can use to author APIs. You can get it from GitHub or you can use the hosted version available at

http://editor.swagger.io

You define your API using YAML in the left pane and on the right you get a live nicely formatted output:

image alt text

After you define your endpoints, you can even test your API right from the browser:

image alt text

As you can see, you can generate boilerplate code for many languages:

image alt text

There are many examples available for you to get started. View them in File > Open Example:

image alt text

image alt text

This is not a full tutorial on the Swagger spec, so let me give you the basics so that you can follow along.

You write some basic information in the info section:

swagger: '2.0'

info:

  version: '1.0.0'

  title: "multi-tier"

  description: "A TodoMVC api"

  contact:

    name: "Jeshan G. BABOOA"

    email: foo@example.com

    url: https://lambdatv.com

  license:

    name: BSD

    url: https://opensource.org/licenses/bsd-2-clause

host: "mw7cji0clf.execute-api.eu-west-1.amazonaws.com"

basePath: /api

schemes:

  - https

consumes:

  - application/json

produces:

  - application/json

You should also specify things like basePath, consumes and produces to define how your API interacts with clients.

Your paths to your API methods and resources go under "path". For each path, you can define your HTTP methods, tell it what parameters it accepts, what response to send back to the browser, etc.

Tip:

I suggest you always enclose the strings in between quotes otherwise, the Swagger editor may not recognise some values like URLs or even separate words as strings.

paths:

  /:

    get:

      description: Returns all todos

      operationId: findTodos

      responses:

        '200':

          description: todos response

          schema:

            type: array

            items:

              $ref: '#/definitions/todo'

        default:

          description: unexpected error

          schema:

            $ref: '#/definitions/errorModel'

You will notice some $ref properties. For example, there’s the response

'200':

  description: todos response

  schema:

    type: array

    items:

      $ref: '#/definitions/todo'

This means that we will return an array of items that are of type "todo" with the HTTP 200 code. “todo” is a custom object. Swagger allows you to define your own models under the definitions section. The following is how we could define a todo with 3 attributes:

definitions:

  todo:

    type: object

    required:

      - id

      - title

    properties:

      id:

        type: integer

        format: int64

      title:

        type: string

      completed:

        type: boolean

Specifying the format can come in handy especially when working with statically typed languages like Java. For example, in the generated SDK for a Java client, an integer type of int64 will be translated to the Java Long class, instead of the regular Integer class:

package io.swagger.client.model;

import java.util.Objects;

import com.google.gson.annotations.SerializedName;

import io.swagger.annotations.ApiModel;

import io.swagger.annotations.ApiModelProperty;

/**

 * Todo

 */

@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2017-03-11T21:51:05.728Z")

public class Todo {

  @SerializedName("id")

  private Long id = null;

  @SerializedName("title")

  private String title = null;

  @SerializedName("completed")

  private Boolean completed = null;

Swagger with API Gateway

Let’s now import this API into API Gateway onto our existing multi-tier api:

https://console.aws.amazon.com/apigateway

image alt text

You can paste what you wrote in the Swagger editor here. But you can also import a Swagger file:

image alt text

API Gateway doesn’t fully support the Swagger spec so you may run into warnings like the following:

image alt text

When API Gateway shows me errors like this, I click ignore just like any good developer would ignore compiler warnings 😃

After that, API Gateway will have overwritten your API with the new definition:

image alt text

You can now start defining which endpoints to use our Lambda function. Repeat the same steps for each HTTP method shown below:

image alt text

You’ll notice that API Gateway has imported the additional data you specified in the Swagger definition file:

image alt text

The documentation in Swagger is also imported:

paths:

  /:

    get:

      description: Returns all todos

      operationId: findTodos

image alt text

Notice the little blue and orange buttons:

image alt text

These are hints to guide you through API Gateway. Feel free to explore them.

The lambda function

Our function could remain almost the same but we’ll have to make some modifications to read input coming from API Gateway as shown here.

console.log('Received event:', JSON.stringify(event, null, 2));

var operation = event.httpMethod;

var body = event.body == null ? {} : JSON.parse(event.body);

var id = undefined;

if (body && body.id) {

    id = parseInt(body.id);

} else if (event.queryStringParameters && 
           event.queryStringParameters.id) {

    id = parseInt(event.queryStringParameters.id);

}

API Gateway doesn’t know that the event object should be of this structure. In the first part, we could get away with it because the "ANY" HTTP method automatically created the object structure for us. For now, we are not using the ANY method. So, we’ll have to tell API Gateway how to construct the event object each time.

We do that by editing the integration request of the method:

image alt text

For GET on the / base resource, we specify a body mapping template with the following body:

{

  "httpMethod": "GET",

  "headers": null

}

We should handle case headers = null to be able to test our function when invoking our function from outside API gateway.

function getDomainOrigin(event) {

    if (event.headers === null) {

        return null;

    }

    if (event.headers.Origin === process.env['ORIGIN']) {

        // allow only this origin to make CORS requests

        return event.headers.Origin;

    }

    return null;

}

image alt text

We repeat the same process for the other 3 methods.

For POST on /

#set($inputRoot = $input.path('$'))
{

  "httpMethod": "POST",

  "headers": null,

  "body": {

    "title" : $input.json('$.title')

  }

}

For GET on /{id}:

{

  "httpMethod": "GET",

  "headers": null,

  "queryStringParameters": {

    "id": $input.params("id")

  }

}

For DELETE on /{id}:

{

  "httpMethod": "DELETE",

  "headers": null,

  "queryStringParameters": {

    "id": $input.params("id")

  }

}

We now deploy our changes so that it’s ready to be used outside the AWS admin console:

image alt text

Make the site faster using CloudFront, Amazon’s CDN

CloudFront can deliver your static site and digital media to a global network of servers to help you deliver a better experience for your users.

To do that, we create a distribution to tell CloudFront from where to pull our site, configure caching, amongst many other things.

Go to https://console.aws.amazon.com/cloudfront

Create a distribution:

image alt text

Click Get Started under Web delivery method:

image alt text

Enter the Origin Domain Name exactly in this format, not the one suggested in the dropdown:

BUCKET_NAME.s3-website-BUCKET_REGION.amazonaws.com

For example, I would input: multitier.jeshan.co.s3-website-eu-west-1.amazonaws.com

image alt text

Choose Yes for Compress Objects Automatically.

image alt text

Leave all the other settings to their default values and create the distribution:

image alt text

This process will take about 10 minutes to deploy your frontend code to Amazon’s global CDN. So wait for the status to turn to Deployed:

image alt text

When it’s complete, your website will now be available at a domain like: https://d2f10lp5lucyco.cloudfront.net/

image alt text

Nice domain, huh? You can now tell your users to find your app here :)

"You’re not seriously telling me to give my users such a domain to type… are you?"

No! To fix that, let’s go back to the Cloudfront distribution you created: to use the domain you chose before

image alt text

image alt text

Put the domain that you picked before as an Alternate Domain Name:

image alt text

Then save it:

image alt text

Also, you’ll likely want your user to always use HTTPS. Let’s configure CloudFront to enforce that for us. You do that by modifying the existing behaviour that you got when you created the distribution:

image alt text

Change the Viewer Protocol Policy to "Redirect HTTP to HTTPS":

image alt text

Then submit the form to save it.

image alt text

Now, you need to update the DNS record that you created earlier. For example, I chose multitier.jeshan.co . So, I change my CNAME record from:

image alt text

to the CloudFront url for the distribution:

image alt text

At this point, we’re still using CloudFront’s SSL certificate. Since we want the users to type our own domain, we need a SSL certificate on our own domain. You can get one for free with AWS itself through its service called AWS Certificate Manager (ACM). You can get there via your CloudFront distribution. Back to where you just edited the Distribution Settings:

Select a Custom SSL certificate and click the Request or Import a Certificate with ACM button:

image alt text

You will be sent to the Certificate Manager screen. Enter a domain, then click Review and Request. Please ensure that you’re requesting the certificate in us-east-1 (N. Virginia) as currently it’s a requirement to be able to use the certificate on CloudFront. On the next screen, click Confirm and Request:

image alt text

You should get an email like the following. Follow the instructions:

image alt text

Approve the certificate request:

image alt text

By the way, you can view the status of your certificate on the ACM console:

https://console.aws.amazon.com/acm/home?region=us-east-1#/

image alt text

Now we need to tell CloudFront to use this certificate. Go back to where we were before and click the refresh button and pick the newly created certificate:

image alt text

Click Yes, Edit to confirm the changes. As usual, the changes take an eternity 10-15 minutes to take effect in CloudFront.

image alt text

When that’s done and the DNS record change has propagated, your website will be available. Type your domain without the http and see that your browser redirects you to https and shows you a nice green lock…

image alt text

with a certificate on our name :)

image alt text

"Is my site really faster now?"

Yes, you can check the difference with the Google PageSpeed insights tool:

Here’s the results without the CDN:

image alt text

Here’s how the site scores when using the CDN:

image alt text

A jump from 36 to 70 (on desktop only) is pretty neat. That’s just by leveraging the CDN. And even despite us not bothering minifying the app and using proper cache headers which is beyond the scope of this channel. If you do that, you can easily score over 90 on both mobile and desktop.

Scripts to automate the process

Except a few cases where manual intervention is a must, like approving a SSL certificate, the process can be fully run via scripts.

There are 3 scripts in all. The reason I’ve them split is to make it clear that you need to provide a certain input for one to be able to run them.

In future tutorials, I’ll make the scripts easier to work with, I’ll be using Python to write deployment scripts as it’s more approachable for most people and we can leverage the boto3 sdk to make it easier to work with AWS APIs.

Recap

Benefits of this architecture (besides AWS’s reliable infrastructure):

More videos coming soon. So, subscribe below:

Stay up to date on serverless on AWS (and nothing else)

* indicates required