Developing Serverless Applications on AWS using AWS Serverless Application Model (SAM)

Dev Ops
10 min readOct 29, 2019

--

What are Serverless Applications on AWS?

Quoting from AWS documentation:

A serverless application is a combination of Lambda functions, event sources, and other resources that work together to perform tasks. Note that a serverless application is more than just a Lambda function — it can include additional resources such as APIs, databases, and event source mappings.

AWS Serverless Application Model (SAM)

The AWS Serverless Application Model (AWS SAM) is an open-source framework that you can use to build serverless applications on AWS. It is developed as a transform for AWS CloudFormation, and supports all features of AWS CloudFormation along with a few additional resources. Combined with the SAM CLI, it serves as a powerful tool to develop, test, build and deploy serverless applications on AWS.

Getting Started with AWS SAM by Amazon Web Services

AWS SAM features

AWS SAM supports all AWS CloudFormation resources along with five additional resource types exclusive to AWS SAM. It also provides an optional Global Section which can be used to define properties common to all SAM resources of the same type. Here is a quick overview of all these features. Click on the section headers to read the official reference for each of these.

AWS::Serverless::Function (Lambda Function)

Creates a Lambda function, IAM execution role, and event source mappings which trigger the function. Since a normal CloudFormation template requires separate resource blocks for each of these, it is much easier to define a Lambda function using AWS SAM.

AWS::Serverless::Api (API Gateway)

Creates a collection of Amazon API Gateway resources and methods that can be invoked through HTTPS endpoints. It is recommended to use Swagger alongside it to define and document the API Gateway, since it provides more control over underlying resources. A resource of this type is implicitly created from the union of API event triggers specified in AWS::Serverless::Function blocks which do not refer to any AWS::Serverless::Api.

AWS::Serverless::Application (Nested Stack)

Embeds a serverless application from the AWS Serverless Application Repository or from an Amazon S3 bucket as a nested application. Nested applications are deployed as nested stacks, which can contain multiple other resources, including other AWS::Serverless::Application resources. You can use this or cross stack references to provide logical separation of different sections of a serverless application.

AWS::Serverless::SimpleTable (DynamoDB Table)

Creates a DynamoDB table with a single attribute primary key. Most table designs have a more complicated design, in which case a AWS::DynamoDB::Table resource can be used instead.

AWS::Serverless::LayerVersion (Lambda Layer)

Creates a Lambda LayerVersion that contains library or runtime code needed by a Lambda function. SAM ensures that old versions of a Lambda Layer are not deleted when a new version is uploaded by changing the Logical ID in the transformation stage.

Globals Section

Resources in a single template can have multiple properties in common with same values like Runtime, Cors, etc. The Globals section can be used to define such shared properties. This helps reduce duplication of resources.

AWS SAM CLI

SAM CLI provides a Lambda-like execution environment that lets you locally build, test, debug, and deploy applications defined by SAM templates. The following commands are supported in AWS SAM CLI.

sam init

This command initializes a sample “hello world” serverless application with an AWS SAM template. The template provides a folder structure for your Lambda functions, and is connected to an event source such as APIs, S3 buckets, or DynamoDB tables. It can be used as a starting point to make your own AWS SAM template.

sam validate

This commands validates an AWS SAM template file.

sam local generate-event

This command generates sample payloads from various event sources. Supported event sources include Alexa Skills Kit, Alexa Smart Home, API Gateway, Batch, Cloudformation, Cloudfront, Cloudwatch, CodeCommit, CodePipeline, Cognito, Config, DynamoDB, Kinesis, Lex, Rekognition, S3, SES, SNS, SQS and StepFunctions

sam local start-api

This command creates a local HTTP server that hosts all of your functions, and can be used for quick development and testing.

sam local start-lambda

This command starts a local endpoint that emulates AWS Lambda. The functions can be locally invoked synchronously (wait for the response), or asynchronously using the AWS CLI or SDK.

sam local invoke

This command invokes a local Lambda function once and quits after invocation completes. This is useful for functions handling asynchronous events (like S3 PUT) or an automated testing script of multiple test cases. When accessed, it starts a Docker Container locally to invoke the function.

sam build

This command builds Lambda source code dependencies and generates deployment artifacts that target Lambda’s execution environment. This can be used to test Lambda functions locally in an environment similar to the AWS Cloud. The build artifacts can be uploaded to S3 by sam package before deployment.

sam package

This command is an alias for aws cloudformation package. It uploads build artifacts to S3, and outputs an updated template file with the path to uploaded artifacts in S3. This updated template can be used to deploy the stack using sam deploy command.

sam deploy

This command is an alias for aws cloudformation deploy. Deploys the specified template by creating and then executing a change set. It updates the existing stack if it already exists and creates a new one if it doesn’t.

sam publish

This command takes a packaged AWS SAM template and uploads it to AWS Serverless Application Repository in specified region.

sam logs

This command is used to fetch logs generated by Lambda functions.

Deploying a simple Hello World Application using AWS SAM

This tutorial guides you through building a simple AWS SAM application consisting of an API Gateway and a Lambda function. The API Gateway has a single GET endpoint which triggers a Lambda function which returns the simple text “hello world”. This tutorial briefly covers all the steps given here along with additional information which makes the steps easier to understand.

Prerequisites

  • Created an AWS account.
  • Configured IAM permissions. (SAM CLI commands require permissions for Lambda, CloudFormation and API Gateway. IAM permssions are required for PassRole action, and for CreateRole if a Role is created implicitly or explicitly in template.)
  • Created an Amazon S3 bucket (required for uploading deployment artifacts like function code).
  • Installed AWS CLI (required for SAM CLI).
  • Installed Docker (if testing application locally).
  • Installed Homebrew (recommended way of installing AWS SAM CLI on Linux or macOS).
  • Installed AWS SAM CLI.

Step 1: Initialize the Hello World Project directory using SAM CLI

$ sam init -r python3.7
[+] Initializing project structure...
Project generated: ./sam-appSteps you can take next within the project folder
===================================================
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json
[*] Start API Gateway locally: sam local start-api
Read sam-app/README.md for further instructions[*] Project initialization is now complete

sam init creates a sample serverless application with ideal directory structure for Lambda functions, Unit Tests and template files. The -r flag can be used to specify the runtime for Lambda functions (in this case, Python 3.7).

We can use the tree command to review the directory structure.

$ tree -I *pyc* sam-app/ # Ignores Python cache files
sam-app/
|-- events
| `-- event.json # Sample API Proxy Event
|-- hello_world
| |-- app.py # Lambda function code
| |-- __init__.py # Marks directory as Python package
| `-- requirements.txt # List of required modules
|-- README.md # README file with instructions
|-- template.yaml
`-- tests
`-- unit
|-- __init__.py # Marks directory as Python package
`-- test_handler.py # Unit test for Lambda code

Let’s go through some of the important files.

template.yamlAWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn

Some important points:

  • The text Transform: AWS::Serverless-2016–10–31 is used to indicate that it is a SAM template.
  • Timeout is defined in Globals section, so any other Lambda function which is added will also have same timeout.
  • API Gateway is created implicitly due to event source mapping in Lambda function.
  • IAM Role for Lambda function is created implicitly. If you do not have permission to create an IAM Role, you can specify an existing role using its ARN in the Role parameter.
hello_world/app.pyimport json# import requestsdef lambda_handler(event, context):# try:
# ip = requests.get("http://checkip.amazonaws.com/")
# except requests.RequestException as e:
# # Send some context about this error to Lambda Logs
# print(e)
# raise ereturn {
"statusCode": 200,
"body": json.dumps({
"message": "hello world",
# "location": ip.text.replace("\n", "")
}),
}
hello_world/requirements.txtrequests

Some important points:

  • The Lambda function code simple returns a response with message as “hello world”. It also has additional code to send IP address in the response which is commented out.
  • The requirements file has a single module, requests. More information on python requirements files can be found here.

Step 2: Build Your Application

$ sam build
Building resource 'HelloWorldFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
Build SucceededBuilt Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Package: sam package --s3-bucket <yourbucket>
$ tree -L 2 .aws-sam/build/ # Restrict to 2 levels
.aws-sam/build/
|-- HelloWorldFunction
| |-- app.py
| |-- certifi
| |-- certifi-2019.9.11.dist-info
| |-- chardet
| |-- chardet-3.0.4.dist-info
| |-- idna
| |-- idna-2.8.dist-info
| |-- __init__.py
| |-- requests
| |-- requests-2.22.0.dist-info
| |-- requirements.txt
| |-- urllib3
| `-- urllib3-1.25.6.dist-info
`-- template.yaml

sam build builds Lambda source code dependencies and generates deployment artifacts that target Lambda’s execution environment. In this sample application, the requests module is downloaded along with its dependencies and placed in the build directory alongside the Lambda function code. The template file references the code inside the build directory.

Step 3: Package Your Application for Deployment

$ sam package --s3-bucket <bucket-name> --output-template-file packaged.yaml --s3-prefix packaged
Uploading to packaged/ea7a02a5833a012572ff34731e258466 262144 / 530830.0 (49.3%)
Uploading to packaged/ea7a02a5833a012572ff34731e258466 524288 / 530830.0 (98.7%)
Uploading to packaged/ea7a02a5833a012572ff34731e258466 530830 / 530830.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file <path>/packaged.yaml --stack-name <YOUR STACK NAME>

sam package uploads build artifacts to S3, and outputs an updated template file with the path to uploaded artifacts in S3. The --s3-prefix flag is used to set the prefix for build artifacts inside the bucket.

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://<bucket-name>/packaged/ea7a02a5833a012572ff34731e258466

Step 4: Deploy Your Application to the AWS Cloud

$ sam deploy --template-file packaged.yaml --region <region-name> --capabilities CAPABILITY_IAM --stack-name hello-world-app
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - hello-world-app

sam deploy deploys the specified template by creating and then executing a change set. It updates the existing stack if it already exists and creates a new one if it doesn’t. The CAPABILITY_IAM flag is required because the stack creates an IAM role implicitly for the Lambda function.

Step 5: Test Your Application in the AWS Cloud

$ aws cloudformation describe-stacks --stack-name hello-world-app --region <region-name> --query "Stacks[].Outputs"
[
[
{
"Description": "API Gateway endpoint URL for Prod stage for Hello World function",
"OutputKey": "HelloWorldApi",
"OutputValue": "https://<restapiid>.execute-api.us-east-1.amazonaws.com/Prod/hello/"
}
]
]
$ curl https://<restapiid>.execute-api.us-east-1.amazonaws.com/Prod/hello/
{"message": "hello world"}

The describe-stacks command retrives information about deployed stacks. The --stack-name flag specified which stack to check and the --query flag can be used to filter the result. The curl command can be used to send a request to the endpoint given in the output and test the response.

Step 6: Testing Your Application Locally

By Hosting API Locally

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-10-29 13:24:20 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
$ curl http://127.0.0.1:3000/hello
{"message": "hello world"}

sam local start-api replicates the online REST API environment locally. It runs the Lambda function inside a Docker container which is similar to the online runtime environment.

Making One-off Invocations

$ sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambda_handler (python3.7)
Fetching lambci/lambda:python3.7 Docker container image......
Mounting /home/ec2-user/sample/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: ed09936e-d40a-1307-7f1f-8ffac844ee65 Version: $LATEST
END RequestId: ed09936e-d40a-1307-7f1f-8ffac844ee65
REPORT RequestId: ed09936e-d40a-1307-7f1f-8ffac844ee65 Duration: 3.45 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 22 MB
{"statusCode":200,"body":"{\"message\": \"hello world\"}"}

sam local invoke directly invokes your Lambda functions, and can pass input event payloads that you provide. With this command, you can pass the event payload using the -e flag. The function runs the function inside a Docker container which is similar to the online runtime environment. The command waits for response from the Lambda function before exiting.

By default, the sample event is of a POST request and not a GET request. Since the function does not read the request body, it still returns the correct response. To generate a more accurate sample event, you can use sam local generate-event

$ sam local generate-event apigateway aws-proxy --body "" --path "hello" --method GET > event-get.json$ sam local invoke "HelloWorldFunction" -e event-get.json
Invoking app.lambda_handler (python3.7)
Fetching lambci/lambda:python3.7 Docker container image......
Mounting /home/ec2-user/sample/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: cb077418-9d0d-13db-1bd8-2c686f9a3429 Version: $LATEST
END RequestId: cb077418-9d0d-13db-1bd8-2c686f9a3429
REPORT RequestId: cb077418-9d0d-13db-1bd8-2c686f9a3429 Duration: 3.42 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 22 MB
{"statusCode":200,"body":"{\"message\": \"hello world\"}"}

Step 7: Cleanup: Destroy Stack

$ aws cloudformation delete-stack --stack-name hello-world-app --region region

aws cloudformation delete-stack deletes all the resources deployed by the stack and the stack itself.

References

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html
https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md

___________________________________________

Kaustubh Khavnekar

Platform Engineer | Quantiphi Inc. | US and India

http://www.quantiphi.com | Analytics is in our DNA

____________________________________________

--

--