Going Serverless With AWS Lambda And API Gateway

AWS Lambda is an event-driven, serverless computing platform by Amazon. You can run your code without provisioning or managing a servers using AWS Lambda. You are charged only for the computing resources actually consumed, which is metered in the multiple of 100 milliseconds. Java, Node.js, C# and Python - are officially supported languages for writing Lambda. If you want to write Lambda in language not supported officially, it could be executed through a thin Node.Js wrapper. I this post, I will show how to write the Lambda in Golang. A Lambda could be executed in response to events like changes in the AWS S3 bucket(i.e. video, image, document processing), changes in DynamoDB table(i.e. data processing, analysis), in response to IoT device events, etc. A Lambda could also be exposed as an API to be consumed by your mobile apps, integration layer or clients.

Here is why I think Lambda could be a really powerful tool for many use cases:

  • (a) No devops/ servers to manage -just write the code to do what you need to, it removes lots of friction
  • (b) It’s executed in high-availability compute infrastructure
  • (c) It could be scaled easily with vast computing power available at AWS
  • (d) Pay for what you used - not need to pay for that extra computing power to deal with spikes in usage

You could write code directly in the AWS console or upload a zip file in S3 bucket. You could specify the amount of memory, maximum execution time, etc. while creating Lambda. When Lambda is invoked, AWS launches an execution environment(sort of a container) base on those settings. These containers are cached for subsequent executions. The default limit on the number of parallel executions is 100, you could request to increase this limit.

Let’s Write Lambda In Golang

Go compiles to single binary and is known for fast execution, which makes it a good candidate to write Lambda in. Golang isn’t officially supported language for writing Lambda yet, but the Node.js wrapper could invoke the executable. You could write a thin Node.js wrapper to invoke the executable, however tool like Sparta makes it even easier and provides additional features like cross compiling for Linux, deployment, local testing, etc. I will use Sparta in this post.

Sparta provides a Main function that transforms your lambda functions into an application. This function should be called from your application’s main package. Let’s write a Lambda for generating QR Code. As you can see qrGenerator is configured as a Lambda function by calling sparta.NewLambda in the main call:

package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/Sirupsen/logrus"
	sparta "github.com/mweagle/Sparta"
	qrcode "github.com/skip2/go-qrcode"
)

//This is our Lambda
func qrGenerator(event *json.RawMessage,
	context *sparta.LambdaContext,
	w http.ResponseWriter,
	logger *logrus.Logger) {

	//Read the event info - we could access Body, Headers, QueryParams, PathParams, Method, etc.
	var lambdaEvent sparta.APIGatewayLambdaJSONEvent
	errEvent := json.Unmarshal([]byte(*event), &lambdaEvent)

	if errEvent != nil {
		logger.Error("Failed to unmarshal event data: ", errEvent.Error())
		http.Error(w, errEvent.Error(), http.StatusInternalServerError)
		return
	}

	//Read the QRRequest
	//fmt.Printf("Request: %s", lambdaEvent.Body)
	var qrRequest QRCodeRequest
	errRequest := json.Unmarshal(lambdaEvent.Body, &qrRequest)

	if errRequest != nil {
		logger.Error("Error reading the QR Code request: ", errRequest.Error())
		http.Error(w, errRequest.Error(), http.StatusInternalServerError)
		return
	}

	//Create the QR image
	var png []byte
	png, err := qrcode.Encode(qrRequest.QRText, qrcode.Medium, 256)

	if err != nil {
		fmt.Printf("Error: %s", err)
	}

	//logger.Info("QR Generator: ", string(*event)) //<-- Logging could be helpful for troubleshooting

	result, err := json.Marshal(OperationResult{
		Success: true,
		Data: QRCode{
			QRText: qrRequest.QRText,
			Image:  png},
		Message: "Successfully generated the QR Code."})

	if err != nil {
		logger.Panic("Error: %s", err)
		panic(err)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	fmt.Fprint(w, string(result))
}

func main() {
	var lambdaFunctions []*sparta.LambdaAWSInfo
	//Configure our Lambda to expose it
	lambdaFn := sparta.NewLambda(sparta.IAMRoleDefinition{}, qrGenerator, nil)
	lambdaFunctions = append(lambdaFunctions, lambdaFn)

	sparta.Main("QRGenerator", "QR Code Utilities", lambdaFunctions, nil, nil)
}

//Just a reusable response wrapper
type OperationResult struct {
	Success bool
	Message string
	Data    interface{}
}

//QRCode response - more details could be added
type QRCode struct {
	QRText string
	Image  []byte
}

//QRCode Request details
type QRCodeRequest struct {
	QRText string `json:"qrtext"`
}

Once you compiled, Sparta make following commands available to your executable.

delete      Delete service
describe    Describe service
execute     Execute
explore     Interactively explore service
provision   Provision service
version     Sparta framework version

Running ./lambdaGo explore will start a local web server and expose your Lambda for testing. You could use curl or Postman to invoke it.

Let’s Deploy It

Sparta uses AWS SDK for provisioning the Lambda, so you need to configure your credentials, region, etc. You can get more information about creating credential file and role from here. You could set the region through environment variable by running export AWS_REGION=us-east-2, make sure this region matches with your bucket’s region. Once you have credentials and region in place, you could run the command ./lambdaGo provision -s <your-bucket-name> to start the provisioning Lambda to AWS. Once deployed to AWS, you will be able to see your Lambda on console and test it

Let’s Expose Lambda as API

Our Lambda is there up in the cloud, but we haven’t configured any triggers or exposed as an API. Let’s use AWS API Gateway to expose it as API. Go to the API Gateway on AWS console and follow the steps for creating an API to execute the Lambda we created in previous step

There are multiple ways to secure your API. I have secured it through the API key and have also created usage plan to limit the number of invocations/ month. Once everything is configured, you could test the set up by invoking through curl or Postman. Make sure to pass the authorization information and/or API key based on your configuration.

AWS console also provides monitoring and logging features.