When it comes to Cloud Computing, we can hear a lot of fantasies here and there about costs. If you only lift and shift to the Cloud, the chances to save money are unlikely. On the contrary, it might be more expansive because you would probably improve your SLA by design.
So, the real power of the cloud, cost-wise, lies more in replatforming and refactoring.
In this article, we will see together how I redesigned and refactored a 3-tier blog application into a serverless one to see how I divided my cloud bills by 12 !
Initial stack
Architecture
The initial architecture of our blog is a common 3-tier with the web tier, application tier and database tier.
- Web server : host the frontend
- App server : host the backend handling the REST requests to execute business logic code and interacting with the database
- Database : host the RDBMS (PotgreSql here) to read/write data
Features
The application is not that complex and only do few actions :
- GET /personalblog/api/articles
- POST /personalblog/api/articles
- GET /personalblog/api/articles/{articleId}
- PUT /personalblog/api/articles/{articleId}
- DELETE /personalblog/api/articles/{articleId}
The OAS contract is defined below :
openapi: 3.0.0
info:
title: Blog API
version: 1.0.0
description: API contract for the core features of a blog.
servers:
- url: https://api.example.com/v1
paths:
/articles:
get:
summary: Retrieve the list of articles
responses:
'200':
description: List of articles retrieved successfully
content:
application/json:
example:
articles:
- id: 1
title: "First article"
content: "Content of the article."
- id: 2
title: "Second article"
content: "Content of the article."
post:
summary: Create a new article
requestBody:
required: true
content:
application/json:
example:
title: "New article"
content: "Content of the new article."
responses:
'201':
description: Article created successfully
content:
application/json:
example:
id: 3
title: "New article"
content: "Content of the new article."
/articles/{articleId}:
parameters:
- name: articleId
in: path
required: true
description: ID of the article
schema:
type: integer
get:
summary: Retrieve details of an article
responses:
'200':
description: Article details retrieved successfully
content:
application/json:
example:
id: 1
title: "First article"
content: "Content of the article."
put:
summary: Update an existing article
requestBody:
required: true
content:
application/json:
example:
title: "Updated article"
content: "New content of the article."
responses:
'200':
description: Article updated successfully
content:
application/json:
example:
id: 1
title: "Updated article"
content: "New content of the article."
delete:
summary: Delete an article
responses:
'204':
description: Article deleted successfully
Technologies
The application is mainly coded with Typescript with :
- Angular for the frontend
- NestJS for the Backend
- PostgreSQL for the database
Infrastructure
The application is hosted with :
- 1 EC2 t3.small (2vCPU/2GB RAM) for the frontend.
- 1 EC2 t3.large (2vCPU/8GB RAM) for the backend : I could have used a t3.small but I wanted to add observability agents and some other stuff on the server.
- 1 Aurora for PostgreSql Serverless instance : this is the lowest price I could get with PostgreSql. EC2, RDS or Aurora provisioned would have been very much expansive.
Infrastructure cost
So, with this configuration, I had around 100$ of monthly cost for the infrastructure.
Let's see now how we can replatform all of this with serverless and what needs to be refactored.
Serverless stack
Architecture
Here is our new serverless architecture for the exact same needs :
- S3 Webhosting : to host the Angular frontend
- API Gateway : to handle REST requests from the frontend and trigger lambda functions
- Lambda functions : to execute the application logic with Typescript
- DynamoDB : to read/write data
Refactoring
To be able to deploy our application to this new architecture, here is what we have to do :
- Infrastucture as code : I used Terraform because the old architecture was made with it. But I could have done it with AWS SAM too.
- Frontend : nothing changed. I was still delivering the
dist
folder of Angular. - Backend : I have split our code in functions and have rewrite the SQL transactions into DynamoDB API calls with AWS SDK.
- IAM : I created the right AWS roles and permissions to be able to make the AWS services to communicate properly.
- CI/CD : this part was time consuming since lambdas do not detect code changes when delivering an unchanged configuration. I had to upload lambda's code into s3 with event triggering to update the code inside the lambda.
All these changes took about 5 days of work to get the things done right.
Infrastructure cost
The final infrastructure cost after all of this replatforming and refactoring was around 7.65$ per month !
So I divided my bill by around 12 !
Other benefits
The whole stack is now scalable and highly available by design. With the old stack, I would have need Load Balancing and multiple EC2 in multiple AZ. It would have doubled my monthly cost to achieve such a goal.
Drawbacks
There are few drawbacks but they can be blockers or not considering your own context :
- We had to invest some time in code refactoring
- Lambda's are considered as vendor lock in. There are some alternatives like K-Native or OpenFaaS but I must admit that it implies changes.
- Cold start : if your lambdas are not invoked at least every hour, they may have a cold start with lower performances. There are many articles talking about this topic and how to avoid it. So you just have to be aware of how to avoid it.
Conclusion
Finally, we've seen that the major savings to be made in the Cloud come after the migration with replatforming and refactoring. It comes with an invest but it can bring you high rewards.