AWS CDK: Lambda Versioning
Tags: aws, cdk, versioning, serverless
Published: 2021-04-23 11:34:32

# 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](https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html), 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](https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/lambda-version-control.html).

Introducing my Serverless API versioning strategy:

![Lambda Versioning Strategy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ann38c6kluep7rc3sipf.png)
 
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:

```javascript
{
  "greeting": "Hello World!",
  "version": "v1"
}
```
The code below is from the CDK stack file:

```typescript
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
});
```
Documentation: [Function](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html), [Version](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Version.html), [Alias](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Alias.html).

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 🖥

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

const v1 = api.root.addResource('v1');
const v1Greeting = v1.addResource('greeting');
v1Greeting.addMethod('GET', new LambdaIntegration(helloLambdaV1Alias));
```
Documentation: [RestApi](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html), [LambdaIntegration](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaIntegration.html).

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.

```typescript
const v2 = api.root.addResource('v2');
const v2Greeting = v2.addResource('greeting');
v2Greeting.addMethod('GET', new LambdaIntegration(helloLambda));
```
Documentation: [RestApi](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html), [LambdaIntegration](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaIntegration.html).

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:
```javascript
{
  "greeting": "Hello World!",
  "version": "$LATEST"
}
```

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](https://docs.aws.amazon.com/cli/latest/reference/lambda/delete-function.html) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uymaj81kbg3f8qoktjdr.png)

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!