API Versioning Approach With AWS API Gateway
In this post, I have tried to describe the steps and artifacts require to implement Amazon API Gateway based REST API versioning strategy.
Join the DZone community and get the full member experience.
Join For FreeAbstract
In this post, I have tried to describe the steps and artifacts require to implement Amazon API Gateway based REST API versioning strategy.
Introduction
There is a requirement to versioning the REST API in one of our projects. These REST APIs should be generic enough to support multitenancy and multiple versions. As these APIs will be used by multiple customers, there is a possibility where two different customers can use two different versions of the API simultaneously.
As we are implementing our API using the Amazon API Gateway service, and we want to implement the recommended best approach. I was in a search to figure out the best versioning strategy using AWS API Gateway and Lambda. After spending a day in search, my understanding is that there are three gross strategies to version the REST API. 1) Create a completely new API with appending the version number at the end (e.g. www.mydomain.com/ordersV1, www.mydomain.com/ordersV2 something like this), 2) Putting the version indicator in the resource path (e.g. www.mydomain.com/api/v1/xxx, www.mydomain.com/api/v2/xxx) — It's a traditional approach. and 3) Create a completely new domain for the new version (v1.api.mysite.com, v2.api.mysite.com). We have to select our approach out of these three strategies.
Solution Approach
There is an option in the AWS API Gateway named Stage Variables. Inside theIntegration Request view, we can provide the stage variable name ( ${stageVariables.<stage variable name> }
) instead of the actual Lambda function name. During deployment, the stage variable can contain a different Lambda version (or alias), so that based on invoked resource (/api/v1/getOrders), staging environment (dev, prod etc.), and lambda name with version defined in stage variable, API Gateway can choose the corresponding function to invoke. We decided to exploit this feature to go with the traditional versioning approach i.e. resource path-based versioning. The next section explains the details of the manual implementation steps of this approach.
Solution Details
I create one simple NodeJs Lambda function with two versions and then created the AWS Gateway API resources to trigger those versions. All these steps described here are executed manually. Later, we will establish the AWS CodePipeline for Lambda version (or alias) deployment, but as per the current plan, we will not be automating the API deployment. It will be done manually as per the roadmap.
1. Create one nodejs lambda function getOrders
.
2. Generate two versions of the function with some changes in the JSON response.
3. Create a test event to test the lambda. As I have a plan to create a LAMBDA_PROXY
type integration in API Gateway, I select the API Gateway AWS Proxy template. The test event JSON request is given below.
{
"body": "{\"test\":\"body\"}",
"resource": "/dev/v1/orders",
"requestContext": {
"resourceId": "123456",
"apiId": "1234567890",
"resourcePath": "/dev/v1/orders",
"httpMethod": "GET",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"accountId": "123456789012",
"identity": {
"apiKey": null,
"userArn": null,
"cognitoAuthenticationType": null,
"caller": null,
"userAgent": "Custom User Agent String",
"user": null,
"cognitoIdentityPoolId": null,
"cognitoIdentityId": null,
"cognitoAuthenticationProvider": null,
"sourceIp": "127.0.0.1",
"accountId": null
},
"stage": "dev"
},
"queryStringParameters": {
"start":"2015-10-01T00:00:00Z",
"end":"2015-10-04T00:00:00Z"
},
"headers": {
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"Accept-Language": "en-US,en;.8",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Mobile-Viewer": "false",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"CloudFront-Viewer-Country": "US",
"Accept": "text/html,application/xhtml+xml,application/xml;.9,image/webp,*/*;.8",
"Upgrade-Insecure-Requests": "1",
"X-Forwarded-Port": "443",
"Host": "1234567890.execute-api.us-east-2.amazonaws.com",
"X-Forwarded-Proto": "https",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"CloudFront-Is-Tablet-Viewer": "false",
"Cache-Control": "",
"User-Agent": "Custom User Agent String",
"CloudFront-Forwarded-Proto": "https",
"Accept-Encoding": "gzip, deflate, sdch"
},
"pathParameters": {
"proxy": "path/to/resource"
},
"httpMethod": "GET",
"stageVariables": {
"v1fn": "getOrders:1",
"v2fn":"getOrders:2"
},
"path": "/path/to/resource"
}
4. Following the minimum changes I did to test the lambda function:
- Update
resource
andresourcePath
with planned API Gateway resource path (/dev/v<#>/orders
). httpMethod
would beGET
for this POC.stage
would bedev
.queryStringParameters
arestart
andend
timestamp.- I have a plan to create two versions of the API, each of which will point to the corresponding Lambda version in dev environment. For that, I have created two
stageVariables
(v1fn
andv2fn
) with <function name>:<version number> (getOrders:1
&getOrders:2
)
That’s it. I am done with the test event creation. If I execute the event, I can see the getOrders function is returning the proper output.
5. Now, from the Amazon API Gateway menu, create an API with the following path for two API versions. For version 1, it is /v1/orders
, and for version 2, it is /v2/orders
.
6. The integration request type is LAMBDA_PROXY
to pass queryStringParameters
seamlessly.
7. To implement the LAMBDA_PROXY
integration type, we need to do the following setup inside the Integration Request view.
Note: Here, instead of giving lambda function name, I specify ${stageVariables.<stage-variable-name>}
to dynamically select a different function version (or alias).
8. The same setup is needed for /v2/orders
resource.
9. Now we can test both the APIs from the API Test wizard.
Note: We have to enter the stage variable value here during the test (e.g. for v2fn
it is getOrders:2
), otherwise, the API will behave incorrectly.
10. Now deploy the API in dev stage. The API URI for different versions are as below:
https://xxxxxxxxxx.execute-api.xxxxxxxxx.amazonaws.com/dev/v1/orders
https://xxxxxxxxxx.execute-api.xxxxxxxxx.amazonaws.com/dev/v2/orders
Where the resources are /dev/v1/orders
and /dev/v2/orders
respectively.
Note: We have a plan to deploy lambda code using the AWS CodePipeline. As we have a limited number of APIs, we will manually create an API from the Amazon API Gateway console.
Conclusion
The following are common issues that we have addressed from this implementation.
Issues |
How we address |
No isolation. If we have a bug in v1 code that can be exploited, all our instances are vulnerable. |
There are separate codes for each of the Lambda functions and as it is a functional programming approach, there is no common code between the versions. |
Development limitations. We need to support the whole code base, making sure "v1" and "v2" live nicely together. Consider a situation when some dependency is used in both "v1" and "v2" but require specific different versions. |
Both v1 and v2 Lambda function codes and their corresponding dependencies are mutually exclusive, so there would be no conflict. |
Technology lock-in. It is either impossible or very hard to have "v1" in C# and "v2" in Python. |
It may not be possible using Lambda implementation because there is 1:1 mapping between the Lambda function and the programming language. |
Capacity planning and monitoring. It is hard to understand how much resources are consumed by "v1" calls vs. "v2." |
We can monitor based on the complete and regular expression-based resource path. |
Domain name-based and API name-based approaches can also be a possible solution, but as Amazon offers us so much flexibility, we are ardent to implement the versioning with its deepest component and can maintain the traditional flavor.
Opinions expressed by DZone contributors are their own.
Comments