The following is a brief discussion of an approach to versioning a serverless API on the AWS stack. In this stack the typical serverless API architecture comprises an API within the API Gateway service that has endpoints connected to Lambda functions. These Lambda functions then perform different actions (i.e. CRUD operations on a datastore).
The approach utilises different components from AWS API Gateway (namely API Cloning, Stages and Stage Variables) and AWS Lambda (Function Versioning, Aliases and Environment Variables). The effort required to manage these features into an effective and combined versioning strategy should be mitigated via a continuous integration.
Versioning in API Gateway
The approach taken to versioning an API in AWS API Gateway depends entirely on the desired API versioning strategy. For example, the following approaches could be taken:
API Versioning Strategies
This approach uses the URI to provide different routes to different versions of the API. For example, /api/v1/products or /api/v2/products. This approach is simple to implement and favours the developer because it's easier to jump between different versions while working/testing.
With regards to API gateway, versioning via the URI means a policy must be defined that dictates what action is taken on each increment of a version. For example, a major version change could require the creation of a new API, which could be routed to using the custom domain feature (i.e. theapi.com/v1 => API 1 & theapi.com/v2 => API 2).
Custom Request Header
In this approach a custom header is added to the HTTP request. For example, api-version: 2 could be added as a header, which dictates which version of the contract/function the API expects to use. This is more complicated than versioning via the URI because the consumer of the API must correctly construct the request to test the API. However, it does provide very granular control of which resources are versioned and how they are consumed.
The content negotiation approach consists of changing the accept-header used in the HTTP request to specify the format of the response data. For example:
Before: HTTP GET: https://domain.com/api/products Accept: application/json After: HTTP GET: https://domain.com/api/products Accept: application/vnd.domain.v2+json
Again this approach increases the complexity involved in consuming the API, but it also provides granular control over each element of the API. The most significant advantage being that we don't have to version an entire API (i.e. all services) when a single resource/service has changed.
Application of this approach in AWS API Gateway is considerably more complex than versioning at the URI. Here it means configuring the Integration Request to interrogate the request headers and direct the request to the correct version of the Lambda API.
Serverless Versioning Implementation
When implementing the requisite versioning strategy in API Gateway there are features that could be used. Primarily these are API Cloning, Stages and Stage Variables.
When creating an API the user is presented with the option to Clone from an existing API. From the perspective of versioning this makes it possible to clone from the previous version of the API and create a new API accessible via a different URI. As previously mentioned it's advisable to only create a new API on a major version change.
In AWS API Gateway a stage is similar to a tag of an API at a point in time. For example, you can define a Development stage and deploy the API to it. Then when the API is ready for release you can deploy to a Production stage. You can also have different custom domains pointing to different stages of the same API.
Stages, when combined with Stage Variables and Lambda Aliases enable us to ensure an API Gateway stage (i.e. Production) is connected to the correct alias of the Lambda Function (i.e. Production).
These are key-value pairs of values that are associated with a specific stage and are similar to Environment Variables supported by AWS Lambda. For example, as previously mentioned we can specify a value for the
lambdaAlias variable that can then be used in the Integration Request configuration for the Lambda function (shown below).
Versioning in Lambda
The main features in AWS Lambda that we can use to support an API versioning strategy are Function Versioning, Aliases and Environment Variables.
Within the Lambda service, a function can have multiple versions all of which have a unique ARN (Amazon Resource Number). This means each version is in effect a separate copy of the feature. However, versions are immutable and once created cannot be changed. To publish changes to a function a new version must be created.
An alias is a pointer to a specific version ($Latest, 1, 2, etc.) of the Lambda function. However, each alias also has a unique ARN and is mutable in that an alias can be updated to point to a different version of a Lambda function.
These are key-value pairs of values that are used to configure parts of the Lambda function. For example, we could configure the table name to use when connecting to a DynamoDb (shown below).
The following example illustrates an approach to managing an API with two versions. Before looking over the example it's important to note that
API V1 and
API V2 are totally separate APIs.
API V2 would have been created by cloning
API V1. Additionally,
Function V1 and
Function V2 are different iterations of the same function (but separate Lambda functions). For illustration,
Function V1 could be called GetProductById and Function V2 called GetProductByIdV2. Therefore, the creation of
Function V2 illustrates a major version change in the Function V1.
Version 1 - Production
This version is accessible by the consumer on the URL "myapi.com/v1/products". This custom domain is configured to point to the Prod Stage on API V1. From the API we use Stage Variables to configure the Lambda Alias used by the Prod Stage (named "Prod"). Then the Prod Alias is configured to point to version 3 of
Function V1 (making it version 1.3).
Version 1 - Development
The developer accesses this instance of the API on the URL "dev.myapi.com/v1/products". This custom domain is configured to point to the Dev Stage on API V1. From the API we use Stage Variables to configure the Lambda Alias used by the Dev Stage (named "Dev"). Then the Dev Alias is configured to link to the $Latest version of
Function V1. The $Latest version is the current instance of the function that has not yet been published.
Version 2 - Production
The consumer accesses this instance of the API on the URL "myapi.com/v2/products". This custom domain is configured to point to the Prod Stage on API V2. From the API we use Stage Variables to configure the Lambda Alias used by the Prod Stage (named "Prod"). Then the Prod Alias is configured to point to version 2 of
Function V2 (making it version 2.2).
Version 2 - Development
The developer accesses this instance of the API on the URL "dev.myapi.com/v2/products". This custom domain is configured to point to the Dev Stage of API V2. From the API we use Stage Variables to configure the Lambda Alias used by the Dev Stage (named "Dev"). Then the Dev Alias is configured to link to the
$Latest version of Function V2. The
$Latest version is the current instance of the function that has not yet been published.
This has been a brief overview of how versioning of serverless APIS may be implemented. This has only been tested out by creating a brief proof-of-concept and therefore requires a good deal more investigation before being integrated into production scenarios.
I'll update this post when (or if) we get this into production!