DEV Community

Cover image for AWS CDK: Lambda Versioning
Gerald Stewart for AWS Community Builders

Posted on • Updated on

AWS CDK: Lambda Versioning

Introduction 👋

I recently set out to investigate a few different ways we could version a Serverless API Gateway backed by Lambda functions. I've seen a few different blogs talking about this issue, but none in a CDK context.

In this blog I'll walk through the strategy I devised to be able to support multiple versions of a Lambda function fronted by an API Gateway, with code examples.

Disclaimer: This may not be the most optimal way to solve this issue, just my approach. If you have a better way I'd love to hear it!

The Problem 🤔

You are developing a API Gateway backed by Lambda functions and you need to be able to make a breaking change to your API without breaking your consumers. Giving consumers time to use your old API version while they make the necessary changes to move the newest version.

In this example, we do not care for making code changes to previous versions of the Lambda.

Lambda Versioning 🗄

Lambda supports versioning, by default if no are versions explicitly defined the $LATEST version will be overwritten. This is fine until you need to support two versions of a function simultaneously.

Lambda versioning is also briefly covered in the AWS Well-Architected Framework Serverless Lens.

Introducing my Serverless API versioning strategy:

Lambda Versioning Strategy

The above allows for active development of the /v2/greeting endpoint while maintaining a active /v1/greeting endpoint buying consumers some time. In my case the end goal is to shut off /v1/greeting once consumers have made the necessary changes.

Lets take a look at how this looks and works using the AWS CDK!

Lambda Code 🖥

In this example, helloLambda is returning a JSON response, this is what it will return in v1 of the Lambda:

{
  "greeting": "Hello World!",
  "version": "v1"
}
Enter fullscreen mode Exit fullscreen mode

The code below is from the CDK stack file:

const helloLambda = new Function(this, 'helloLambda', {
  runtime: Runtime.NODEJS_14_X,
  code: Code.fromAsset('lambda-fns'),
  handler: 'index.handler',
});

const helloLambdaV1 = new Version(this, helloLambda-v1, {
  lambda: helloLambda
});

const helloLambdaV1Alias = new Alias(this, helloLambda-v1-alias, {
  aliasName: 'helloLambda-v1',
  version: helloLambdaV1
});
Enter fullscreen mode Exit fullscreen mode

Documentation: Function, Version, Alias.

This code snippet defines a new Lambda function helloLambda and creates a new version helloLambda-v1. An Alias is created helloLambda-v1-alias that is associated with the version helloLambda-v1.

Next we need to set up two API endpoints, going to different versions of our helloLambda.

API Gateway Code 🖥

const api = new RestApi(this, 'greeting-api');

const v1 = api.root.addResource('v1');
const v1Greeting = v1.addResource('greeting');
v1Greeting.addMethod('GET', new LambdaIntegration(helloLambdaV1Alias));
Enter fullscreen mode Exit fullscreen mode

Documentation: RestApi, LambdaIntegration.

This leaves us with a /v1/greeting endpoint pointing to the alias helloLambdaV1Alias. The next step is to add a /v2/greeting endpoint allowing us to point to the $LATEST version of the function so we can continue to develop the newest version of our API.

const v2 = api.root.addResource('v2');
const v2Greeting = v2.addResource('greeting');
v2Greeting.addMethod('GET', new LambdaIntegration(helloLambda));
Enter fullscreen mode Exit fullscreen mode

Documentation: RestApi, LambdaIntegration.

It's important to note, if you want to point to $LATEST do not define a new Lambda version. In the v2 code snippet above we reference helloLambda directly instead of referencing the version helloLambdaV1Alias.

Reference, Alias and Function are all accepted types for setting up a new LambdaIntegration.

As we now have /v2/greeting pointing to $LATEST version of the Lambda function, we can update the Lambda handler code to return a different hardcoded value:

{
  "greeting": "Hello World!",
  "version": "$LATEST"
}
Enter fullscreen mode Exit fullscreen mode

Deploying this updated handler code would make it return "version": "$LATEST" when calling the /v2/greeting endpoint and return "version": "v1" when calling the /v1/greeting endpoint.

Summary 🤌

This pattern is good for supporting basic API versioning. I was also amazed at how easy this was to accomplish with the AWS CDK and felt I had to share it!

In order to clean up old Lambda versions you need to use the AWS CLI lambda delete-function command specifying a qualifier to target specific versions you no longer need.

Alternative Solution 🤷‍♂️

If you require backwards support for code changes I'm unaware of anything besides duplicating Lambda functions for each supported version that can accomplish that.

Lambda Versioning Strategy - Dupe the Lambda

This has it's drawbacks, duplicating code is never good. However if you do need full backwards support it can still work.

If you've tried either of these strategies, or you have a better one I'd love to hear about it in the comments!

Top comments (3)

Collapse
 
rehanvdm profile image
Rehan van der Merwe

I always duplicate the code and make a whole new API GW like you suggest in your Alternative Solutions. Have had too many times where we had to change/fix a bug in the older version. Also if you loose the Lambda, you loose your version history, that seems very risky?

Collapse
 
_gerald20 profile image
Gerald Stewart

That's actually sort of relieving to hear! After I ran these by the rest of the team we decided to go with that for that reason.

Collapse
 
ranjith_jaga profile image
Ranjith Jagadees

I don't think, this would work. You are creating a new lambda version and attaching it to v1, then attaching the lambda to v2 endpoint. If you change your lambda code and deploy this stack, lambda will create a new version and attach it to v1 endpoint and v2 endpoint will point to the lambda. So both the endpoints point to the same code.