Mastering AWS CDK Part 2: Leveraging Custom Constructs

Mastering AWS CDK Part 2: Leveraging Custom Constructs

This article was written by Iggy Yuson. Iggy, a DevOps engineer based in the Philippines, specializes in cloud-native applications on AWS. He has a broad skill set in creating comprehensive solutions for web and mobile platforms and excels in deploying serverless architectures within AWS.

In the previous article, we explored the basics of AWS CDK, emphasizing the utilization of AWS’s pre-made components. We analyzed a simple Serverless REST API structure, incorporating three key serverless offerings from AWS: Amazon API Gateway as the entry point for the REST API, AWS Lambda for computation, and Amazon DynamoDB for data storage.

The Fat Lambda

In contrast to the simple architecture we covered earlier, it's essential to acknowledge that such a configuration isn't typically considered best practice for production-grade applications. Within the serverless community, there's often discussion about the concept of "Fat Lambda," which refers to situations where the majority, if not all, of the backend code is consolidated into a single Lambda Function. Considering our previous article, the coding methodology employed in our Lambda Function could potentially classify it as a "Fat Lambda."

The code provided represents the handler function of the Lambda Function we developed previously. As evident, the code's flow progresses from top to bottom based on the event received by the Lambda Function. As we continue to expand our application and incorporate additional features, this Lambda Function will inevitably grow in complexity, resembling something like this:

This code snippet specifically represents the 'handler' for our Lambda Function, excluding the underlying logic functions such as 'writeToDynamoDB', 'readAllFromDynamoDB', 'editDataFromDynamoDB', and 'deleteDataFromDynamoDB'. In a "Fat Lambda" architecture, as the Lambda Function expands with more features and complexity, it might grow too large to manage within the AWS Management Console efficiently. A notable illustration of a "Fat Lambda" is incorporating an entire ExpressJS application within a single Lambda Function. Some experts in serverless architecture often referred to as a "Lambda-lith" to describe this, likening it to a monolithic application encapsulated within a Lambda Function.

In building production-grade serverless applications, it's advisable to embrace the single responsibility principle, a well-regarded concept in the serverless community. This principle advocates for assigning each Lambda Function a singular, clearly defined task. While this strategy may present challenges in code sharing and maintenance across Lambda Functions, it delivers substantial advantages in terms of flexibility and optimization. Furthermore, this architectural approach aids in minimizing cold starts by virtue of each Lambda Function serving as a smaller, more specialized logic unit.

Returning to AWS CDK to gain a deeper understanding of this tutorial, we'll progress our architecture to we'll be advancing our architecture to resemble the following:

Following the single responsibility principle when designing Lambda Functions is commonly acknowledged as a best practice, particularly for serverless applications intended for production use. This methodology involves assigning each Lambda Function a distinct and singular purpose. Although scaling the application could lead to a larger number of Lambda Functions, which may add complexity to management tasks, this issue can be addressed by leveraging Infrastructure as Code (IaC) tools such as AWS CDK.

IaC tools, like AWS CDK, excel in simplifying the administration of serverless setups. They facilitate the efficient fine-tuning and setup of AWS assets, such as numerous Lambda Functions, a task that proves difficult when relying solely on the AWS Management Console. Operating a complex architecture housing hundreds of Lambda Functions without an IaC solution can be overwhelming. Even more importantly, the lack of these tools increases the risk of security vulnerabilities, potentially opening the door to cyber-attacks that pose a significant threat to the enterprise's integrity and existence.

Prerequisites

  1. Set up a development environment or development account in AWS.
  2. Install Node, AWS CDK, and the AWS CLI.
  3. Configure AWS credentials locally.
  4. Implemented the tutorial in the previous article entitled: “Mastering AWS CDK Part 1: Using Built-In Constructs for Serverless APIs”

Implementation with Built-In Constructs

Having already delved into building a serverless REST API using AWS CDK, implementing our new architecture should seem relatively uncomplicated. Following the same steps as before should make the thought process quite simple.

  1. Create 3 Lambda Functions.
  2. Authorize each Lambda Function to both read from and write to Amazon DynamoDB.
  3. Attach the same Lambda Layer to each Lambda Function
  4. Attach each Lambda Function to the API Gateway, ensuring each function is linked to the appropriate path and method.

Following this approach, within the lib/tutorials-dojo-cdk-app-stack.ts file, we initiate the creation of three AWS CDK Lambda Function constructs.

Next, we need to synchronize the defined Lambda constructs with the file structure of our current project. Here's the required action plan:

  • Within the lambda directory, establish three additional directories named as follows: getLambda, postLambda, and deleteLambda.
  • In each of these newly established directories, create a file named index.ts.

Here's how the revised file structure appears:

Now, insert the provided code into the newly created index.ts files for each respective Lambda Function.

For the lambda/getLambda/index.ts:

For the lambda/postLambda/index.ts:

For the lambda/deleteLambda/index.ts:

Next, we need to authorize each Lambda Function to read from and write to the DynamoDB table. Returning to the lib/tutorials-dojo-cdk-app-stack.ts file, we can achieve this by adding the following lines of code for each Lambda Function:

Next, we need to attach the current Lambda Layer to these three Lambda Functions.

Having already provided the three Lambda Functions with read and write access to the DynamoDB table and the Lambda Layer, the last step is to integrate each Lambda Function with the API Gateway. The LambdaIntegration construct is utilized for each Lambda Function's integration into the API Gateway.

Reviewing the diagram, access to all Lambda Functions occurs via the API Gateway using the /sample path, each employing distinct methods. Specifically, getLambda is accessed through a GET method,  postLambda through a POST method, and deleteLambda through a DELETE method. To achieve this, we utilize the existing root resource to establish the /sample resource on top of it.

Lastly, utilizing this sample resource, we assign a method to each of the respective Lambda Functions.

The structure of lib/tutorials-dojo-cdk-app-stack.ts should look like the following:

Implementation with Custom Constructs

Upon reviewing the code presented, the code appears to be somewhat unusual. Notably, two key coding principles, DRY (Don’t Repeat Yourself) and KISS (Keep It Short and Simple), seem to be disregarded in the implementation. Specifically, in the implementation found in lib/tutorials-dojo-cdk-app-stack.ts, there's excessive repetition of code. This repetition includes three instances of duplicated Lambda Function constructs, followed by repetition of DynamoDB permissions, Lambda Layer access, and finally, integration of each Lambda Function with the API Gateway.

A notable aspect of AWS CDK is its capability to create custom constructs. For a clearer understanding, a custom construct in AWS CDK essentially combines various constructs. If you're acquainted with the frontend framework React, the idea resembles crafting custom React components or hooks. With AWS CDK, users have the ability to create abstraction layers that accommodate the distinct standards and needs of an organization through custom constructs.

To create a custom construct, it's imperative to analyze the structure of our existing CDK application. With a clear understanding of the repeating components within our stack, we can proceed to develop a custom construct. Start by establishing a directory named "constructs" for storing custom constructs. Within the constructs directory, generate a file named "TDLambdaApiIntegration.ts". This will result in the following file structure:

When creating the TDLambdaApiIntegrtion.ts custom construct, our initial step involves defining the props it will utilize. To achieve this, we revisit our implementation with built-in constructs. Despite utilizing built-in constructs, the props' values may differ for each repetition of constructs. For instance, the code prop in the Lambda Function construct and the method prop in the sample resource construct. These props can serve as props for our custom construct. The first prop for our custom construct should be dynamoDBTable, indicating which DynamoDB table grants read and write access to the Lambda Function. Subsequently, the resource prop identifies the resource utilized by the custom construct. Following this, the sourceCodePath prop specifies the location of the Lambda Function within our current file structure. An optional prop, lambdaLayer, is included as not all Lambda Functions necessitate a Lambda Layer. Lastly, the method prop denotes the method utilized to invoke the Lambda Function through the API Gateway. When declaring the props in the constructs/TDLambdaApiIntegrtion.ts file, the structure should resemble the following:

Here's how the custom construct should appear:

In summary, here's the structure of constructs/TDLambdaApiIntegrtion.ts:

Upon examination, the custom construct mirrors our thought process. Initially, we create a Lambda Function, assign it read and write permissions to the DynamoDB table, incorporate a Lambda Layer, and ultimately link the Lambda Function to the API Gateway with the appropriate path and method. Now, the pivotal question arises: how do we implement this custom construct? The solution is straightforward: utilize it similarly to any other construct.

In the file lib/tutorials-dojo-cdk-app-stack.ts, we begin by importing our newly created custom construct:

Next, within the stack, we declare our custom construct.

This construct essentially generates a Lambda Function situated in the lambda/getLambda directory, equipped with read and write permissions to the specified DynamoDB Table. It's linked to the sampleResource resource and the GET method within this resource of the declared API Gateway. Moreover, it incorporates the designated Lambda Layer. We iterate through this process for the remaining Lambda Functions.

Deployment of the CDK Application

Once all these components have been established within the lib/tutorials-dojo-cdk-app-stack.ts stack, the file should appear as follows:

To initiate the deployment of the TutorialsDojoCdkAppStack AWS CDK Stack, execute the following command from the root directory:

Testing the Serverless REST API

Prerequisites

  1. Ensure Postman is installed.
  2. Configure the base URL of the REST API as an environment variable in Postman. This URL will be shown in the terminal after the Stack is deployed.
  3. Create a Postman Collection.

Testing

Initially, we'll test the POST request. Ensure to input the accurate path, which is {{url}}/sample. The request body should be in JSON format, where 'id' is required. Refer to the setup provided below.

After clicking "Send," anticipate a response resembling the format demonstrated above.

Next, we'll test the GET request. Replicate the initial POST request, but change it to a GET request. The path remains the same; however, this time, there won't be any request body. Refer to the configuration below.

Once you click "Send," you'll observe a successful response displayed above, indicating the newly posted object sent via the POST request.

Finally, we'll test the DELETE request. Similar to the POST request, the DELETE request will include a request body in the same format. Refer to the configuration below.

After clicking "Send," you'll receive a successful response above. This will show the deleted item, and subsequent GET requests will no longer display that deleted item.

Final Remarks

This article extensively explores the advanced functionalities of AWS CDK, highlighting its powerful capabilities in constructing and managing intricate serverless architectures. The shift from fundamental constructs to customized ones demonstrates the adaptability and effectiveness of AWS CDK, particularly in upholding best practices such as the single responsibility principle. This methodology not only streamlines the administration of serverless systems but also notably enhances their scalability, maintainability, and security. The practical examples provided serve as a thorough guide, showcasing the effectiveness of AWS CDK in developing robust, production-ready serverless applications.

Moreover, the exploration of the drawbacks associated with "Fat Lambda" architectures and the focus on modular Lambda Function designs highlight the significance of deliberate architecture in cloud-based applications. Through the utilization of AWS CDK's advanced functionalities, developers can create serverless solutions that are more streamlined, dependable, and scalable. Consequently, this article serves as a crucial resource for individuals aiming to unlock the complete capabilities of AWS CDK in developing cutting-edge serverless applications, guaranteeing that their endeavors are not only technically proficient but also in accordance with industry standards.

* This newsletter was sourced from this Tutorials Dojo Article.

Haitham Khalid

Manager Sales | Customer Relations, New Business Development

9mo

Excited to dive into this insightful read on AWS CDK! Jon Bonso

Like
Reply

To view or add a comment, sign in

More articles by Jon Bonso

Insights from the community

Others also viewed

Explore topics