Cloudfront can be simply defined as a CDN (Content Delivery Network), caching your static assets in a datacenter nearer to your viewers. But Cloudfront is a lot more complex and versatile than this simple definition. Cloudfront is a “pull” CDN, which means that you don’t push your content to the CDN. The content is pulled into the CDN Edge from the origin at the first request of any piece of content.
In addition to the traditional pull and cache usage, Cloudfront can also be used as:
A Networking Router
A Firewall
A Web Server
An Application Server
Why is using a CDN relevant?
The main reason is to improve the speed of delivery of static content. By caching the content on the CDN edge, you not only reduce the download time from a few seconds to a few milliseconds, but you also reduce the load and amount of requests on your backend (Network, IO, CPU, Memory, …).
Static content can be defined as content not changing between two identical requests done in the same time frame.
Identical can be as simple as the same URI, or as fine grained as down to the authentication header. The time frame can range between 1 second to 1 year. The most common case is caching resources like Javascript or CSS and serving the same file to all users forever. But caching a JSON response tailored to a user (Authentication header) for a few seconds reduces the backend calls when the user has the well-known “frenetic browser reload syndrome”.
Edges, Mid-Tier Caches, and Origins
Cloudfront isn’t “just” some servers in datacenters around the world. The service is a layered network of Edge Locations and Regional Edge Caches (or Mid-Tier Caches).
Edge Locations are distributed around the globe with more than 400 points of presence in over 90 cities across 48 countries. Each Edge Location is connected to one of the 13 Regional Edge Caches.
Regional Edge Caches are transparent to you and your visitors, you can’t configure them or access them directly. Your visitors will interact with the nearest Edge Location, which will connect to the attached Regional Edge Cache and finally to your origin. Therefore, in this article, we will refer to Cloudfront as the combination of Edge Locations and Region Edge Caches.
What Have We Learned?
Cloudfront is more than just a simple “pull-cache-serve” service
You improve delivery speed to your visitors
You can increase resilience by always using a healthy backend
You improve overall speed to your backend by leveraging AWS’s backbone
You can modify any request to tailor the response to your visitor’s device or region
You don’t always need a backend
You protect your backend by reducing the number of calls reaching it
CloudGTO is a platform that aims to help developers of any experience level accelerate building and deploying applications using the Serverless Framework. It is still in active development phase but public beta access is available. This is an excellent opportunity to check out CloudGTO and provide some feedback and suggestions to the team!
The Service Builder uses a wizard-like interface to help build out the desired infrastructure. It supports common Serverless patterns for REST APIs. It allows customizations, such as choosing to enable Tracing or not, and selecting the appropriate memory settings for Lambda Functions, to name a few. The builder is flexible and allows additional resources to be created as well.
The Service Builder generates a downloadable 'serverless.zip' file which contains the IaC templates and Lambda Function code for performing CRUD operations against DynamoDB tables. The project structure is organized in an opinionated manner by incorporating best practices and leaning into the experience of Serverless developers who work extensively with the technology.
Building Meal Prep Service REST APIs
In this blog post, we are going to use CloudGTO to help build REST APIs for a Meal Prep Service.
In the following architecture diagram, we configure our API Routes and Methods in API Gateway and use Lambda Proxy Integration to invoke the appropriate Lambda Functions based on the request.
We create a Cognito User Pool to secure the Order Management APIs (Orders and Order Line Items). We only allows users with a valid Cognito Identity to place orders.
We will store the Orders in a DynamoDB table using the Single Table Design model. For our example, an Order can have 1 or more Line Items. Each Line Item will include the meal ID selected, quantity, unit price and sub total. When an order is checked out, we calculate the total price due by adding the sub total for the line items.
To highlight the flexibility offered by CloudGTO, we create an additional DynamoDB table to store our Menu. Menu items simply include the meal ID, price, seller and category. Menu items can be viewed by Seller or by Category (pork/chicken/seafood/vegetarian/beef).
Notice that we opted to keep the Menu APIs available and open to everyone. This is because we want potential customers to see the options we have to offer.
Meal Prep Service Architecture
If you wish to follow along with the example, you will need the following:
I would like to share my experience and some of the things I learned using CloudGTO to build the Meal Prep REST API Services.
We will discuss the REST API Blueprints currently provided and share some thoughts on how we decided to go with the CloudGTO recommended design.
We also share how CloudGTO features helped us adhere to AWS Best Practices.
CloudGTO Promotes Adoption of Proven Serverless Patterns
CloudGTO currently supports the 3 most common patterns (aka Blueprints) for building Serverless REST APIs.
CloudGTO Available Blueprints
Monolithic
The Monolithic Lambda pattern utilizes a single Lambda Function to support the different HTTP methods allowed by the 'ANY' method. Because we are allowing all possible HTTP methods, the Lambda Function will need to perform a lot more work to determine which method was requested and whether it is a supported method. The Lambda Function also gets significantly larger in size as it has to include all the code for all the methods. These are factors that can impact a function’s overall performance, deployment speed, maintainability, observability, cost, etc. Least Privilege Security is also harder to achieve as the Lambda Function will need full read/write access to the DynamoDB table. Troubleshooting also gets a lot more cumbersome.
All that being said, this is still a valid pattern that can be considered to support migrations from Monolithic APIs to Microservices architecture.
Monolithic Lambda Architecture
Single Lambda Many Routes
The Single Lambda Many Routes pattern breaks up the different HTTP methods into their own routes. This provides a bit more flexibility as each route can be managed at a more granular level in API Gateway. For example, we can choose to secure certain routes via Cognito, while leaving others open and available. A single Lambda Function supports all the available API routes, but we at least have a well defined idea on what is supported. In this example, we know for sure that only GET/POST/PUT methods are supported by the '/items' route. Most of the disadvantages of the Monolithic approach also apply to this pattern.
Single Lambda Many Routes Architecture
Single Lambda Per Route
This is the recommended pattern for building Serverless REST APIs, which is what we picked when we built our service.
This choice allowed us to adhere to security best practices. Because each Lambda has a single responsibility, implementing least privilege security is simplified.
Keeping functions lean and simple makes code easier to maintain. Lean functions generate smaller packages which can speed up deployments and improve overall performance. Cold start latencies can be reduced as well.
Scalability and observability can be as granular as we need them to be. This helps provide insights on how our services are doing and find optimization opportunities.
Single Lambda Per Route Architecture
Comparison Chart for the Patterns
Here is a summary of the what we just talked about and the pillars we used to drive our decision:
CloudGTO Simplifies Organization and Management of Cloud Resources
Naming Conventions
While building our service, we noticed that inputs for resource names are restricted to certain lengths. This is intended to allow CloudGTO to add prefixes or suffixes to the provided base name. Useful information like Region, Stage and Resource Type are typically good tokens to easily identify what a resource is for.
Isolated Stack Management
CloudGTO uses Serverless Compose to manage the infrastructure. A CFN Stack is created for each group of resources (1 stack for each 'serverless.yml' file).
It created 5 CFN stacks as shown in the image below.
Stack per serverless.yml
Infrastructure components that tend to remain static (infrequent updates), and do not have code, are grouped in the 'resources/' folder. Another advantage of managing individual CFN stacks is reducing the risk of accidental updates to unintended resources.
For example, suppose we have a single stack that includes both DynamoDB and Lambda Functions. The Lambda Functions will be updated more frequently while the DynamoDB table most likely remains unchanged after it is created. There are times when deployment errors occur in CloudFormation, and unfortunately, the easiest and simplest way to address this issue is to delete and recreate the stack. In our example scenario, we would risk losing data if we do choose, to delete the stack. In my opinion, having Monolithic CFN Stacks complicates resource management, leads to tight coupling, causes headaches and reduces the options we have for fixing deployment issues quickly.
Every approach has its own pros and cons. What we like about managing isolated stacks is how much it simplifies everything and reduces possible mistakes during manual stack management tasks. This helps simplify and speed up deployments as well.
CloudGTO Emphasizes Least Privilege Security Principle
The Service Builder allows us to customize permissions to be granted to our Lambda Function. Because we opted for the recommended blueprint for REST APIs, we can be as granular as we need when assigning permissions. This allows us to apply the principle of least privilege security in our environment.
The image below shows how we can apply the minimum required permissions for the Lambda Execution Role in CloudGTO:
Least Privilege Security
To further illustrate, let’s focus on the operations implemented by the Orders API. Each Lambda Function is responsible for a single task only. As a result, we only need to grant each function the minimum permissions required to perform its job.
Orders CRUD APIs
'createOrder' function creates a new Order. We only grant it 'create' permissions against the table.
'getOrder' function retrieves an Order from the Orders table. We grant it 'read-only' permissions against the table.
'updateOrder' function updates details for an Order. We only grant 'update' permissions against the table.
'deleteOrder' function deletes an Order. We only grant it 'delete' permissions against the table.
CloudGTO Alleviates Repetitive, Tedious and Boilerplate Tasks from Developers
Dev Tools Configuration
CloudGTO sets up common configurations for Development Tools such as ESLint, Prettier, Webpack. These usually remain static between projects. Instead of having to copy/paste reusable configuration files from old to new projects, CloudGTO takes care of this for us!
Auto Generated Starter Code for Lambda Functions
CloudGTO generates code that performs simple CRUD operations against DynamoDB. The code can be deployed as is if we do not have additional logic or requirements to implement. We can also modify the code to meet our needs, which is what we did for our sample project. Because there is already code to start, it speeds up the development process tremendously. It’s always easier to work off of something that having to create everything from scratch.
Auto Generated Unit Tests
CloudGTO also generates 'Jest' unit tests based on the starter code generated. Of course, as we make code changes, the tests will need updated. But again, it is nice to already have the scaffolding and configurations in place.
The 'events/' folder provides event templates that are used for testing.
CloudGTO is flexible and allows us to extend its opinionated approach with the ability to add additional resources to support our application.
In our meal prep service, we created an additional DynamoDB table to store Menu Items.
The image shows additional resource types that can be added to the infrastructure:
Additional Resource Types
When providing the Table Name, you will notice that it restricts the length of the base name you define so it can prefix/suffix it with useful metadata details.
The Builder also allows us to select our Billing Mode. By default, it is 'PAY PER REQUEST'.
CloudGTO supports both Simple and Composite Primary Keys.
Setup Table Name, Billing Method, and Primary Key
We can also add Global Secondary Indexes to support additional access patterns. GSIs also support both Simple and Composite Primary Keys. The GSIs inherit the table’s Billing Method.
The billing mode for a Global Secondary Index is inherited from the table
We also added additional Lambda functions that will perform CRUD operations for the Menu DynamoDB table.
We can customize the Runtime, Memory Configuration, Timeout, and Tracing (default is OFF) options as shown in the image:
Set the runtime, memory, and timeout. Optionally, you may choose to enable Tracing for your function
CloudGTO provides a summary of all the resources that will become part of our infrastructure.
Resource List
We set up the routes and methods for our REST APIs. The default integration type is Lambda Proxy. It also infers that we want to use the Cognito User Pool to secure our endpoints. In our example, we want both existing and new customers to see the available meal options. As a result, we opted not to secure the '/menu' endpoints with Cognito. However, in order to place orders, users must have a valid Cognito identity.
Code Changes for Customizing Generated Code
I wanted to highlight a few of the code changes I had to make in order to make the generated code work for my business logic.
Some of these changes were driven by CloudGTO limitations. I’ve shared feedback with the CloudGTO team so these may become native functionality in the future.
DynamoDB Query by Primary Key is the Default Behavior
CloudGTO generates code for CRUD operations with the assumption that we will query the DynamoDB table by primary key. However, in our example, we want to return the complete list of menu items so we will need to perform a scan operation.
The following code snippet shows the original generated code by CloudGTO:
CloudGTO Generates Code for Queries Against DynamoDB Tables Only (Not GSIs)
By default, CloudGTO generates code that interacts only with the DynamoDB table. The code does not include running queries against Global Secondary Indexes. Since I have REST APIs that rely on the GSI, I needed to write some code for it (really, I asked Bedrock for some help 😃).
The code below is generated by the CloudGTO Service Builder. As we can see, it assumes that we will query the table by Primary Key.
Because we need to query the table by a different attribute value, we have created a Global Secondary Index with a Primary Key of 'category_id'
We need to query the GSI in order to group the Menu Items by Category. The following code includes our changes:
Missing GSI Permissions
I also received an error when calling API endpoints that relied on reading the GSIs. This is because CloudGTO only generates permissions for the table. Additional permissions need to be added manually for GSI access.
Notice, in the original function definition, we only grant permissions to the table:
CloudGTO will deploy to your 'default' AWS Account. This is based on the AWS Credentials/Profiles you have configured.
Because I needed the service deployed to my 'dev' account, I needed to specify that by setting the 'AWS_PROFILE' variable right before invoking 'npm run setup', which takes care of installing dependencies and deployment ('AWS_PROFILE=dev npm run setup').
I kept forgetting to set the variable and end up wondering where my resources are at 😂
I found 'cross-env' which helps address my problem. This makes any variable assignment in the command line to work across various platforms (Mac/Windows/Linux…).
The rest of the code changes can be found in this Github Repository Pull Request. I wanted to show what we’ve changed from the original CloudGTO-generated code files.
Instructions for how to test deployed service can be found in this ReadMe.
Conclusion
CloudGTO helped me accelerate the development of the REST APIs for a Meal Prep Service. It is a great kick-starter because to me, getting started is the hardest part of building a project. Since CloudGTO Blueprints adhere to Serverless best practices, I did not have to worry about creating anti-patterns and problems for myself. It saved so much time by taking care of boilerplate configurations, code, project setup/organization, and unit tests! As someone who is fairly new to the Serverless Framework and NodeJS, it is also a great learning tool! I did not have to 'google' or research as much because CloudGTO took care of the heavy lifting for me.
This is just the beginning for CloudGTO. The team is working diligently to improve the current features, address bugs, and come up with new functionality.
So, what are you waiting for? 😉 Give it a shot!! Let us know what you think.
We would love to receive some feedback so we can continue to make the product work better for Serverless builders!
Need to reach the team?
You can reach CloudGTO team via email: [contact@cloudgto.com] or by submitting feedback using the link in the top right corner of the Service Builder Start Page (you will only see this once you have created an account to access the public beta).
Report a bug by using the button on the bottom left hand corner of the Service Builder Start Page:
Feedback/Comments/Thoughts
Thank you very much for your time! I would love to hear some feedback so I can improve on my knowledge and writing. If you have any requests on Serverless topics you want to learn more about, let us know!!
Kimberly Lu
Senior Serverless Developer
Board game enthusiast who enables fun escapades for my cat Charlie. Foodie who loves to cook and bake. Enjoys 3D printing. A jokester.