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:
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"
}
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
});
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));
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));
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"
}
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.
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)
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?
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.
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.