If you look at the reference architecture diagram, there are three sections highlighted. Here, AWS CloudFront service acts as the main entry point for the application, where we utilize its proxy and routing capabilities. For instance, when a request comes from the user to the web application, CloudFront decides, whether it is an API call or loading of frontend assets based on simple routing rules called behaviors, and forward the request to the correct to the relevant frontend or backend. But, when we need to implement user-wise versioning, the routing should be intelligent enough to identify who is the user that is visiting the application and forward the request to the relevant version of the frontend and backend. Let’s look at how we can implement it in detail.

1. Routing Module

For the routing module to operate, we need to send some sort of user identifier with each request to find out the user. Typically, we need to ask the user to login first before directing him to the relevant application version.

Here, the routing module consists of an AWS Edge Lamda function, a type of Lambda function that is attached to the AWS CloudFront, and a database to store the user and application version mapping. Since the AWS CloudFront triggers the Edge Lambda function, the function code can intercept the request headers, path, or query parameters to identify the user. To facilitate this, we need to send the user identifier from the browser for each subsequent request.

The user identifier then matched with the application version (frontend or backend), which is stored in the user-version map storage database to perform a URL rewrite. You have to write the code to and deploy it to the Lambda function to perform this mapping.

Besides, you need to implement a mechanism to update the user-version map storage depending on your use case. For instance, if you present the user inviting to try out your latest look and feel, the user himself will update the preferred version facilitated by your application in a user-friendly manner.

2. Frontend Versions

Today, we use frontend technologies like Angular, React, VueJS to develop web application frontends. These frontend codebases consist of static assets, which we store in AWS S3 storage. Using AWS Cloudfront, we can serve the frontend directly to the browser. Since we serve multiple versions, we can use the version number for the name of the subpath(folder) inside the S3 bucket (e.g., <bucketname>/v1.0.1/<frontend-assets>) and route to the particular version with the help of the routing module.

3. Backend Versions

For the application backend, we can use AWS API Gateway, Lambda, and a Serverless Database (optional) offering such as AWS Aurora Serverless or AWS DynamoDB for the technology stack. The advantage of using this stack is, AWS API Gateway and Lambda inherently support versioning. We can deploy different stages (e.g., using version number v1.0.1 for the stage name) of the API Gateway to represent each version that contains significant modifications to the backend logic. If we keep the changes to the database backward compatible, we can run multiple versions of the backend connecting to the same database.

With this setup, in the routing module, we can rewrite the stage part of the API gateway based on the user and the Backend API version mapping.

Consumption-Based Cost Model

You might wonder, isn’t these multiple versions cost you more, and is it worth it? Since we select serverless technologies (AWS CloudFront, Lambda, API Gateway &, etc.) in AWS that offer consumption-based pricing, the cost won’t increase while running several versions of the frontend and backend. In consumption-based pricing, each service will charge based on the number of requests triggered by the user actions. For example, dividing the user into 25% and 75% segments pointing to two versions of the API Gateway and Lambda stack costs almost the same as having a single version of API Gateway and Lambda serving 100% of the users.

Besides, even though having more files in the S3 bucket for different versions of the frontend appears to adds cost for storage, which will likely fall well within the AWS Free Tier limits. The same cost structure is applicable for AWS CloudFront and Edge Lambda.

Summary

When adopting the reference architecture, its essential to consider the priorities of the application before jumping directly into the implementation. Depending on the nature of the web application and its requirements, you might face several challenges. Governance of releases, development best practices, and versioning strategies are few of them.

Besides, if the application requires to utilize many other capabilities like caching, asynchronous processing, and realtime communication, changes to these will require you to manage separately. Therefore it is essential to identify these challenges and adapt your solution architecture supporting these use cases.

Besides, it is not necessary to deploy a new version of the backend and frontend per each modification in code. You can asses the gravity of the changes and define your versioning scheme to decide when you want to deploy the new version or updating hte existing version. One best practice is to follow the learnings from semantic versioning, where we do Major, Minor, and Patch updates accordingly.

Also, you can consider this solution for a group of users by modifying the routing logic by user segments.

Learn More