Construct and Stack in AWS CDK
Hola Amigos,
In this article I am going to discuss about Stack and Constructs in AWS CDK.
Constructs
A construct in cdk is typically a building block using which we create different resources for our Infrastructure.
Construct can be consisting of either one or more aws resource.
To create resources CDK provides us 3 levels ( or categories) of constructs in all the languages that CDK supports. You can import the construct initialize it and see it getting created in AWS console.
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
export interface MyConstructProps {
bucketName: string;
}
export class MyConstruct extends cdk.Construct {
constructor(scope: cdk.Construct, id: string, props: MyConstructProps) {
super(scope, id);
new s3.Bucket(this, 'MyBucket', {
bucketName: props.bucketName,
versioned: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
});
}
}
Now let's go through all different levels of constructs provided by AWS.
L1 level
L1 level constructs are most customizable and lowest level of construct in terms of abstraction.
It allows you to handle every single setting related to the single (one construct creates only one resource e..g S3 bucket, DynamoDb etc.) resource you are creating, so normally this level of constructs are for those who are already familiar and have experience with AWS CDK.
L1 level constructs are also called CFN resources, so to identify all constructs that has "Cfn" in it are L1 level constructs.
This should be used only when you need all the control over the settings of the resource you are creating.
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
export class MyL1ConstructStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Define an L1 construct for an S3 bucket
new s3.CfnBucket(this, 'MyL1Bucket', {
bucketName: 'my-l1-unique-bucket-name',
versioningConfiguration: {
status: 'Enabled'
},
// You can add more properties as per your requirements
});
}
}
L2 level
L2 level also creates a single resource like L1 but they have medium level of abstraction and provides us just the right amount of customization needed to create the resource.
This level provides us sensible defaults and helper methods to create a resource. Helper methods are there to change something much after we have initialized a resource.
These are most commonly used Constructs.
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
export class MyL2ConstructStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Define an L2 construct for an S3 bucket
new s3.Bucket(this, 'MyL2Bucket', {
bucketName: 'my-l2-unique-bucket-name',
versioned: true, // Enable versioning
removalPolicy: cdk.RemovalPolicy.DESTROY, // Apply removal policy
autoDeleteObjects: true // Automatically delete objects when the bucket is deleted
});
}
}
L3 level
L3 constructs, also known as patterns, are the highest-level of abstraction.
Each L3 construct can contain a collection of resources that are configured to work together to accomplish a specific task or service within your application.
L3 constructs are used to create entire AWS architectures for particular use cases in your application.
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
import * as lambda from '@aws-cdk/aws-lambda';
import * as s3Notifications from '@aws-cdk/aws-s3-notifications';
export interface MyL3ConstructProps {
bucketName: string;
lambdaFunctionCode: lambda.Code;
}
export class MyL3Construct extends cdk.Construct {
constructor(scope: cdk.Construct, id: string, props: MyL3ConstructProps) {
super(scope, id);
// Create an S3 bucket
const bucket = new s3.Bucket(this, 'MyL3Bucket', {
bucketName: props.bucketName,
versioned: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true
});
// Create a Lambda function
const func = new lambda.Function(this, 'MyL3LambdaFunction', {
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.handler',
code: props.lambdaFunctionCode
});
// Add a notification to the bucket to trigger the Lambda function on object creation
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3Notifications.LambdaDestination(func));
}
}
Custom Construct
In addition to using existing constructs, you can also write your own constructs and let anyone use them in their apps. All constructs are equal in the AWS CDK. Constructs from the AWS Construct Library are treated the same as a construct from a third-party library published via NPM, Maven, or PyPI. Constructs published to your company's internal package repository are also treated in the same way.
To declare a new construct, create a class that extends the Construct base class, in the constructs
package, then follow the pattern for initializer arguments.
export interface NotifyingBucketProps {
prefix?: string;
}
export class NotifyingBucket extends Construct {
constructor(scope: Construct, id: string, props: NotifyingBucketProps = {}) {
super(scope, id);
const bucket = new s3.Bucket(this, 'bucket');
const topic = new sns.Topic(this, 'topic');
bucket.addObjectCreatedNotification(new s3notify.SnsDestination(topic),
{ prefix: props.prefix });
}
}
Stacks
An AWS Cloud Development Kit (AWS CDK) stack is a collection of one or more constructs, which define AWS resources. Each CDK stack represents an AWS CloudFormation stack in your CDK app. At deployment, constructs within a stack are provisioned as a single unit, called an AWS CloudFormation stack.
How to define a stack
Stacks are defined within the context of an app. You define a stack using the Stack
construct from the AWS Construct Library. Stacks can be defined in any of the following ways:
The following example defines a CDK app that contains two stacks:
const app = new App();
new MyFirstStack(app, 'stack1');
new MySecondStack(app, 'stack2');
app.synth();
The following example is a common pattern for defining a stack on a separate file. Here, we extend or inherit the Stack
class and define a constructor that accepts scope
, id
, and props
.
class HelloCdkStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
//...
}
}
However, this code has only declared a stack. For the stack to actually be synthesized into an AWS CloudFormation template and deployed, it must be instantiated. And, like all CDK constructs, it must be instantiated in some context. The App
is that context.
In AWS CDK development template, your stacks are instantiated in the same file where you instantiate the App
object.
The app and stack construct
The App
and Stack
classes from the AWS Construct Library are unique constructs. Compared to other constructs, they don't configure AWS resources on their own.
Instead, they are used to provide context for your other constructs. All constructs that represent AWS resources must be defined, directly or indirectly, within the scope of a Stack
construct. Stack
constructs are defined within the scope of an App
construct.
import { App, Stack, StackProps } from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
class HelloCdkStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
new s3.Bucket(this, 'MyFirstBucket', {
versioned: true
});
}
}
const app = new App();
new HelloCdkStack(app, "HelloCdkStack");
Subscribe to my newsletter
Read articles from Adarsh Kumar Verma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Adarsh Kumar Verma
Adarsh Kumar Verma
Software Engineer with 3+ years of experience (as of July'24), here to blog my learnings and learn from others.