I will be sharing what my team and I learned when we migrated one of our applications from the Serverless Framework to AWS CDK.
Introduction to serverless
What is serverless?
Serverless is a different way of developing. Rather than having a dedicated server (e.g., an EC2 instance) where you have to install the packages, configure runtimes, and cyber harden it.
With serverless, you can rely on services to do what you need (e.g., compute, database, and storage).
It doesn't mean you don't have servers. It means you rely more on your provider's services or functionality (e.g., AWS) provides.
Types of serverless services
There are different types of serverless services.
The most commonly known are for functions or code execution. That would be Lambda.
APIs (that would be API Gateway).
Databases (like DynamoDB).
Object storage (like S3).
There are also other things you can do with orchestration. You can use CloudWatch events, CICD with CodePipeline, CodeBuild, AI, and ML with SageMaker.
You can integrate all these services together depending on your architecture.
Why choose serverless?
There are different reasons, but it comes down to allowing you to be more agile and focus more on building and developing rather than maintaining, securing, and configuring.
It comes in handy when you're building prototypes, have a small team, or want to provide the most value with the least amount of maintenance effort.
Serverless computing, designs, and architectures are not silver bullets. Sometimes, dedicated servers are more beneficial in some designs. These are just some common reasons why you would want to use it.
An example of a serverless architecture on AWS
This is an example of a serverless architecture built on AWS.
We have a serverless website that runs a single-page application that a browser can use to run that web app.
You can use the CloudFront service to deploy a content delivery network that provides caching at edge routers. That way, the web application loads much faster when a user visits it.
You can use the S3 service for website hosting. You can connect the CloudFront and the bucket so any HTML, CSS, or JavaScript file and image file that is loaded from the bucket gets cached via the CloudFront service.
You can also build up your DNS (domain name service) on Route 53 or use an external domain name service. If you use Route 53, then the automatic deployment of any DNS records is managed through the AWS processes.
The backend can also be serverless by using an API gateway to send HTTP events to Lambda functions. A lambda function will run its code. It can get data from a database (like a DynamoDB table) if needed.
The web app will communicate with your backend, and then if you have a mobile app, that too can make API calls to the API gateway.
Using the Serverless Framework
History
To build our application, we previously used the Serverless Framework to build a serverless web app, an API, and its backend. We also built a mobile app that used its own serverless API.
We chose the framework because the team was highly experienced with serverless development and the Serverless Framework.
The architecture
This application had a web app and a mobile app. The web app had its own dedicated API, and so did the mobile app. They shared common backends, databases, and Lambda functions.
Serverless Framework services
The serverless framework is designed for services. When you use the Serverless Framework CLI create command, it creates a new service.
You run the create command in different folders if you want multiple services. This follows the microservices concept, which is very popular.
There were four key services: the platform, which is the core functionality of our application (i.e., the backend); the account service (i.e., the identity provider) that authenticates users; the APIs for the mobile app and the web app to get data from the backend; the web app since it could be standalone.
Platform service
The platform service allowed us to set up the hosted zone, which is where the DNS is set up.
We created our SSL certificates using the certificate manager.
Our Lambda functions provided the code to power our APIs and to do the initial setup when we did onboarding or set up the application in a new AWS account.
We used IAM for the security policies.
We used EventBridge for the background processes.
We also had a core set of DynamoDB tables shared across the application's different parts.
We used various plugins to do different things. You will notice that there was a common set of plugins that will show up in all services.
We built a reusable Serverless Framework service for use in future services and projects.
Accounts service
In the accounts service, we used Cognito. We used one user pool specifically for the web app and another one for the mobile app.
The web app had one type of user. The mobile app had another type of user. Each type of user had different roles and functionality. Therefore, we felt keeping them separate with different user pools was important.
There was also some data that we wanted to keep specific to the account that we chose not to store in Cognito. As a result, we had account DynamoDB tables.
APIs service
We used GraphQL for the API instead of using a RESTful API. We had dedicated APIs for the web app and mobile app.
Web app service
The web app is a serverless web app. It used an S3 bucket, CloudFront distribution, and any related certificates from the platform service.
Findings
We had pretty good success. We were able to deploy a minimum viable product pretty quickly.
We built a common library. We were using a mono repo with a common library folder and any common code shared across the different services.
We found that the deployment order could be tricky sometimes. Some plugins require you to run a pre-deploy command. Others required you to do a post-deploy command. Sometimes, it had dependencies on other plugins. Other times, one service relied on another service's resource that had not been created yet.
YAML was great, but as the file got bigger, reading it was a little confusing sometimes.
The most "annoying" part was creating the IAM policies. There were some plugins that were great. They worked well in Serverless Framework version 1, but there were breaking changes in version 2. Some plugins stopped working in version 3. Using the native CloudFormation way of creating IAM policies was the most reliable, but can be tricky for some.
Regardless, the Serverless Framework was still awesome to build and deploy. We created and deployed our MVP within months.
Moving to AWS CDK
As a team, in 2021, we decided to try out this AWS CDK. It was at version 1 at the time and at least over 100 in the minor version.
We had heard successes with CDK. We decided to experiment with CDK for future projects. We were building MVPs and trying out different product ideas internally.
We tried CDK on one and found that it was a different way of thinking than the Serverless Framework. We had to think about apps and not microservices. Each Serverless Framework service was a CloudFormation stack, but a CDK app could consist of multiple stacks. Both CDK and Serverless Framework use CloudFormation in the underlying deployment method, but their organization is different.
We also found using SSM parameters was much easier to work with in CDK than in Serverless Framework.
In Serverless Framework, we used variables inside of the YAML code. We had to use a plugin to help interpret those variables since native support was limited.
Another nice finding was building reusable components. CDK has level one (L1) and level two (L2) constructs that are ready to be used to deploy certain resources on services. You can also combine level two and level one constructs to build a level three construct. L3 constructs provide a lot better reuse.
Custom level 3 constructs
These are some of the L3 constructs we built.
We created a construct for the hosted zone and certificate so we could create the DNS and any required certificates.
We created a function and log group construct. We found that log groups are automatically created the first time a function is invoked. By creating the log group, we can configure its retention settings and other settings. Doing this will help lower costs by deleting old logs.
We created an L3 construct for the web app app. We also used an existing L3 construct for deploying the web app.
We created a CICI pipeline to deploy our CDK apps. We would deploy applications in multiple AWS accounts, and they were the same app, so having a consistent pipeline was important.
Serverless Framework services to CDK apps
We needed to think about how we wanted to reorganize our Serverless Framework apps to CDK apps.
Web app CDK app
The web app service was the one service that had the least dependencies. We could deploy it by itself without a custom domain and have no external dependencies.
Backend CDK app
We found most of our services and their resources could be in a single backend CDK app since they are related to each other.
We started with databases since we had a Serverless Framework service with just database tables. We created a database stack.
We moved all the account services to a mobile app authentication stack and a web app authentication stack.
We also moved the API services to a web app API stack and a mobile API stack. We specified the database and authentication stacks as dependencies. Doing this allows CDK to determine the deployment order automatically.
We took a new approach to the pre-deploy and post-deploy commands. In Serverless Framework, we used plugins used in the CLI. For CDK, we created a setup stack that we would run as part of the deployment order. It would run the setup for the first deployment. It would not run again after that since there were no changes to any of the CloudFormation resources.
What did we learn from migrating to AWS CDK from Serverless Framework?
More stuff comes "out of the box"
We were able to use a lot of constructs that just came out of the box with CDK. And we could use these constructs to create L3 constructs.
We did not need any plugins. Plugins were essential in Serverless Framework but were not required in CDK.
Serverless Framework provides plugins for creating hosted zones and certificates, but this came native to CDK.
Building Lambda function packages also came native to CDK.
Better organization
We were able to organize our CDK apps better instead of having four main services. And we eliminated the need for pre-deploy and post-deploy commands for each service. We tried to put all the dependencies in their respective stacks. For those we couldn't, we specified which stacks depended on other stacks.
CDK also has a synthesis that is able to catch issues like circular dependencies before you try to deploy.
Working with SSM was easier, and we did not need dotenv files or plugins to work with environment variables.
Simpler deployments and better security
We also found it easier to work with deployments and security.
We only needed one CDK deploy command for each CDK app versus having pre-deploy, deploy, and post-deploy commands for each Serverless Framework service.
Creating IAM roles could be as simple as using a grant function.
CICD deployment was simpler. We created a build spec for each CDK app and used our L3 construct to create the pipeline.
Watch the CDK Day 2023 presentation
Before you go
Here are other posts you might enjoy.
https://medium.com/chapter-by-chapter/aws-cdk-serverless-cookbook-ebook-1d4d4e0488c
https://miguelacallesmba.medium.com/using-docker-for-aws-cdk-development-7054086deb3d
https://aws.plainenglish.io/speed-up-aws-cdk-deploys-up-to-80-c47afad1c18
https://medium.com/illumination/build-robust-cloud-architectures-d512fb9eae1a
https://medium.com/serverless-is-cool/introduction-to-cloud-computing-security-a37b6c4bc4f1
Top comments (0)