This is the AWS CDK v2 Developer Guide. The older CDK v1 entered
maintenance on June 1, 2022 and will now receive only critical bug fixes and security patches.
New features will be developed for CDK v2 exclusively. Support for CDK v1 will
end entirely on June 1, 2023.
Constructs
Constructs are the basic building blocks of AWS CDK apps. A construct represents a "cloud component" and
encapsulates everything AWS CloudFormation needs to create the component.
Constructs are part of the Construct Programming Model (CPM). They're also used by other
tools such as CDK for Terraform (CDKtf), CDK for Kubernetes (CDK8s), and
Projen.
A construct can represent a single AWS resource, such as an Amazon Simple Storage Service (Amazon S3) bucket. A
construct can also be a higher-level abstraction consisting of multiple related AWS resources.
Examples of such components include a worker queue with its associated compute capacity, or a
scheduled job with monitoring resources and a dashboard.
The AWS CDK includes a collection of constructs called the AWS Construct Library, containing
constructs for every AWS service. Construct Hub is a resource to help you discover additional constructs from AWS,
third parties, and the open-source CDK community.
In AWS CDK v1, the Construct
base class was in the CDK core
module. In CDK v2, there is a separate module called constructs
that
contains this class.
AWS Construct library
The AWS CDK includes the AWS Construct Library,
which contains constructs representing AWS resources.
This library includes constructs that represent all the resources available on AWS. For
example, the s3.Bucket
class
represents an Amazon S3 bucket, and the dynamodb.Table
class represents an Amazon DynamoDB table.
L1 constructs
There are three different levels of constructs in this library, beginning with low-level
constructs, which we call CFN Resources (or L1, short for "layer 1"). These constructs directly represent all resources
available in AWS CloudFormation. CFN Resources are periodically generated from the AWS CloudFormation Resource
Specification. They are named CfnXyz, where Xyz is name of the
resource. For example, CfnBucket represents
the AWS::S3::Bucket AWS CloudFormation resource. When you use Cfn resources, you must explicitly
configure all resource properties. This requires a complete understanding of the details of
the underlying AWS CloudFormation resource model.
L2 constructs
The next level of constructs, L2, also represent AWS
resources, but with a higher-level, intent-based API. They provide similar functionality,
but incorporate the defaults, boilerplate, and glue logic you'd be writing yourself with a
CFN Resource construct. AWS constructs offer convenient defaults and reduce the need to know all
the details about the AWS resources they represent. They also provide convenience methods
that make it simpler to work with the resource. For example, the s3.Bucket class
represents an Amazon S3 bucket with additional properties and methods, such as bucket.addLifeCycleRule(), which adds a lifecycle rule to the bucket.
L3 constructs
Finally, the AWS Construct Library includes L3 constructs,
which we call patterns. These constructs are designed to
help you complete common tasks in AWS, often involving multiple kinds of resources. For
example, the aws-ecs-patterns.ApplicationLoadBalancedFargateService construct represents an
architecture that includes an AWS Fargate container cluster employing an Application Load Balancer. The aws-apigateway.LambdaRestApi construct represents an Amazon API Gateway API that's backed
by an AWS Lambda function.
For more information about how to navigate the library and discover constructs that can
help you build your apps, see the API Reference.
Composition
Composition is the key pattern for defining higher-level abstractions
through constructs. A high-level construct can be composed from any number of lower-level
constructs. In turn, those could be composed from even lower-level constructs, which
eventually are composed from AWS resources.
From a bottom-up perspective, you use constructs to organize the individual AWS
resources that you want to deploy. You use whatever abstractions are convenient for your
purpose, with as many layers as you need.
Composition lets you define reusable components and share them like any other code. For
example, a team can define a construct that implements the company's best practice for a DynamoDB
table with backup, global replication, automatic scaling, and monitoring. The team can share
the construct with other teams in their organization, or publicly.
Teams can use this construct in their preferred programming language like they would use
any other library package to define their tables and comply with best practices. When the
library is updated, developers will get access to the new version's bug fixes and improvements
through the workflows they already have for their other types of code.
Initialization
Constructs are implemented in classes that extend the Construct
base
class. You define a construct by instantiating the class. All constructs take three parameters
when they are initialized:
-
scope — The construct's parent or owner, either a
stack or another construct, which determines its place in the construct tree. You should usually pass
this
(or self
in Python), which represents the current object,
for the scope.
-
id — An identifier that must be unique within this scope. The identifier serves as a
namespace for everything that's defined within the current construct. It's used to
generate unique identifiers such as resource
names and AWS CloudFormation logical IDs.
-
props — A set of properties or keyword arguments,
depending upon the language, that define the construct's initial configuration. In most
cases, constructs provide sensible defaults, and if all props elements are optional, you
can omit the props parameter completely.
Identifiers need only be unique within a scope. This lets you instantiate and reuse
constructs without concern for the constructs and identifiers they might contain, and enables
composing constructs into higher-level abstractions. In addition, scopes make it possible to
refer to groups of constructs all at once. Examples include for tagging, or specifying where the
constructs will be deployed.
Apps and stacks
We call your CDK application an app, which is represented by the
AWS CDK class App. The following example defines an app with a single stack that contains a
single Amazon S3 bucket with versioning enabled:
- TypeScript
-
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");
- JavaScript
-
const { App , Stack } = require('aws-cdk-lib');
const s3 = require('aws-cdk-lib/aws-s3');
class HelloCdkStack extends Stack {
constructor(scope, id, props) {
super(scope, id, props);
new s3.Bucket(this, 'MyFirstBucket', {
versioned: true
});
}
}
const app = new App();
new HelloCdkStack(app, "HelloCdkStack");
- Python
-
from aws_cdk import App, Stack
import aws_cdk.aws_s3 as s3
from constructs import Construct
class HelloCdkStack(Stack):
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
s3.Bucket(self, "MyFirstBucket", versioned=True)
app = App()
HelloCdkStack(app, "HelloCdkStack")
- Java
-
import software.amazon.awscdk.*;
import software.amazon.awscdk.services.s3.*;
public class HelloCdkStack extends Stack {
public HelloCdkStack(final Construct scope, final String id) {
this(scope, id, null);
}
public HelloCdkStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);
Bucket.Builder.create(this, "MyFirstBucket")
.versioned(true).build();
}
}
- C#
-
using Amazon.CDK;
using Amazon.CDK.AWS.S3;
namespace HelloCdkApp
{
internal static class Program
{
public static void Main(string[] args)
{
var app = new App();
new HelloCdkStack(app, "HelloCdkStack");
app.Synth();
}
}
public class HelloCdkStack : Stack
{
public HelloCdkStack(Construct scope, string id, IStackProps props=null) : base(scope, id, props)
{
new Bucket(this, "MyFirstBucket", new BucketProps { Versioned = true });
}
}
}
- Go
-
func NewHelloCdkStack(scope constructs.Construct, id string, props *HelloCdkStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
awss3.NewBucket(stack, jsii.String("MyFirstBucket"), &awss3.BucketProps{
Versioned: jsii.Bool(true),
})
return stack
}
As you can see, you need a scope within which to define your bucket. Resources eventually
need to be deployed as part of an AWS CloudFormation stack into an AWS environment. The environment covers a specific
AWS account and AWS Region. AWS constructs, such as s3.Bucket
, must be
defined within the scope of a Stack.
Stacks in AWS CDK apps extend the Stack base class, as shown in the previous
example. The following example is a common pattern when creating a stack within your AWS CDK app. It shows how to
extend the Stack class, define a constructor that accepts scope, id, and props, and invoke the
base class constructor via super
with the received scope, id, and props.
- TypeScript
-
class HelloCdkStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
//...
}
}
- JavaScript
-
class HelloCdkStack extends Stack {
constructor(scope, id, props) {
super(scope, id, props);
//...
}
}
- Python
-
class HelloCdkStack(Stack):
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
# ...
- Java
-
public class HelloCdkStack extends Stack {
public HelloCdkStack(final Construct scope, final String id) {
this(scope, id, null);
}
public HelloCdkStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);
// ...
}
}
- C#
-
public class HelloCdkStack : Stack
{
public HelloCdkStack(Construct scope, string id, IStackProps props=null) : base(scope, id, props)
{
//...
}
}
Using L1 constructs
Once you have defined a stack, you can populate it with resources by instantiating
constructs. First, we'll do it with an L1 construct.
L1 constructs are exactly the resources defined by AWS CloudFormation—no more, no less. You must provide the
resource's required configuration yourself. Here, for example, is how to create an Amazon S3 bucket using the
CfnBucket
class. (You'll see a similar definition using the Bucket
class in the next
section.)
- TypeScript
-
const bucket = new s3.CfnBucket(this, "MyBucket", {
bucketName: "MyBucket"
});
- JavaScript
-
const bucket = new s3.CfnBucket(this, "MyBucket", {
bucketName: "MyBucket"
});
- Python
-
bucket = s3.CfnBucket(self, "MyBucket", bucket_name="MyBucket")
- Java
-
CfnBucket bucket = new CfnBucket.Builder().bucketName("MyBucket").build();
- C#
-
var bucket = new CfnBucket(this, "MyBucket", new CfnBucketProps
{
BucketName= "MyBucket"
});
- Go
-
awss3.NewCfnBucket(stack, jsii.String("MyBucket"), &awss3.CfnBucketProps{
BucketName: jsii.String("MyBucket"),
})
Construct properties that aren't simple Booleans, strings, numbers, or containers are
handled differently in the supported languages.
- TypeScript
-
const bucket = new s3.CfnBucket(this, "MyBucket", {
bucketName: "MyBucket",
corsConfiguration: {
corsRules: [{
allowedOrigins: ["*"],
allowedMethods: ["GET"]
}]
}
});
- JavaScript
-
const bucket = new s3.CfnBucket(this, "MyBucket", {
bucketName: "MyBucket",
corsConfiguration: {
corsRules: [{
allowedOrigins: ["*"],
allowedMethods: ["GET"]
}]
}
});
- Python
-
In Python, these properties are represented by types defined as inner classes of the L1 construct. For
example, the optional property cors_configuration
of a CfnBucket
requires a wrapper of
type CfnBucket.CorsConfigurationProperty
. Here we are defining cors_configuration
on a
CfnBucket
instance.
bucket = CfnBucket(self, "MyBucket", bucket_name="MyBucket",
cors_configuration=CfnBucket.CorsConfigurationProperty(
cors_rules=[CfnBucket.CorsRuleProperty(
allowed_origins=["*"],
allowed_methods=["GET"]
)]
)
)
- Java
-
In Java, these properties are represented by types defined as inner classes of the L1 construct. For
example, the optional property corsConfiguration
of a CfnBucket
requires a wrapper of
type CfnBucket.CorsConfigurationProperty
. Here we are defining corsConfiguration
on a
CfnBucket
instance.
CfnBucket bucket = CfnBucket.Builder.create(this, "MyBucket")
.bucketName("MyBucket")
.corsConfiguration(new CfnBucket.CorsConfigurationProperty.Builder()
.corsRules(Arrays.asList(new CfnBucket.CorsRuleProperty.Builder()
.allowedOrigins(Arrays.asList("*"))
.allowedMethods(Arrays.asList("GET"))
.build()))
.build())
.build();
- C#
-
In C#, these properties are represented by types defined as inner classes of the L1 construct. For
example, the optional property CorsConfiguration
of a CfnBucket
requires a wrapper of
type CfnBucket.CorsConfigurationProperty
. Here we are defining CorsConfiguration
on a
CfnBucket
instance.
var bucket = new CfnBucket(this, "MyBucket", new CfnBucketProps
{
BucketName = "MyBucket",
CorsConfiguration = new CfnBucket.CorsConfigurationProperty
{
CorsRules = new object[] {
new CfnBucket.CorsRuleProperty
{
AllowedOrigins = new string[] { "*" },
AllowedMethods = new string[] { "GET" },
}
}
}
});
- Go
-
In Go, these types are named using the name of the L1 construct, an underscore, and
the property name. For example, the optional property CorsConfiguration
of
a CfnBucket
requires a wrapper of type
CfnBucket_CorsConfigurationProperty
. Here we are defining
CorsConfiguration
on a CfnBucket
instance.
awss3.NewCfnBucket(stack, jsii.String("MyBucket"), &awss3.CfnBucketProps{
BucketName: jsii.String("MyBucket"),
CorsConfiguration: &awss3.CfnBucket_CorsConfigurationProperty{
CorsRules: []awss3.CorsRule{
awss3.CorsRule{
AllowedOrigins: jsii.Strings("*"),
AllowedMethods: &[]awss3.HttpMethods{"GET"},
},
},
},
})
You can't use L2 property types with L1 constructs, or vice versa. When working with L1 constructs, always
use the types defined for the L1 construct you're using. Do not use types from other L1 constructs (some may have
the same name, but they are not the same type).
Some of our language-specific API references currently have errors in the paths to L1
property types, or don't document these classes at all. We hope to fix this soon. In the
meantime, remember that such types are always inner classes of the L1 construct they are
used with.
Using L2 constructs
The following example defines an Amazon S3 bucket by creating an instance of the Bucket
class, an L2 construct.
- TypeScript
-
import * as s3 from 'aws-cdk-lib/aws-s3';
// "this" is HelloCdkStack
new s3.Bucket(this, 'MyFirstBucket', {
versioned: true
});
- JavaScript
-
const s3 = require('aws-cdk-lib/aws-s3');
// "this" is HelloCdkStack
new s3.Bucket(this, 'MyFirstBucket', {
versioned: true
});
- Python
-
import aws_cdk.aws_s3 as s3
# "self" is HelloCdkStack
s3.Bucket(self, "MyFirstBucket", versioned=True)
- Java
-
import software.amazon.awscdk.services.s3.*;
public class HelloCdkStack extends Stack {
public HelloCdkStack(final Construct scope, final String id) {
this(scope, id, null);
}
public HelloCdkStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);
Bucket.Builder.create(this, "MyFirstBucket")
.versioned(true).build();
}
}
- C#
-
using Amazon.CDK.AWS.S3;
// "this" is HelloCdkStack
new Bucket(this, "MyFirstBucket", new BucketProps
{
Versioned = true
});
- Go
-
import (
"github.com/aws/aws-cdk-go/awscdk/v2/awss3"
"github.com/aws/jsii-runtime-go"
)
// stack is HelloCdkStack
awss3.NewBucket(stack, jsii.String("MyFirstBucket"), &awss3.BucketProps{
Versioned: jsii.Bool(true),
})>
The AWS Construct Library includes constructs that represent many AWS
resources.
MyFirstBucket
is not the name of the bucket that AWS CloudFormation creates. It is a
logical identifier given to the new construct. See Physical Names
for details.
Configuration
Most constructs accept props
as their third argument (or in Python, keyword
arguments), a name/value collection that defines the construct's configuration. The following
example defines a bucket with AWS Key Management Service (AWS KMS) encryption and static website hosting enabled.
Since it does not explicitly specify an encryption key, the Bucket
construct
defines a new kms.Key
and associates it with the bucket.
- TypeScript
-
new s3.Bucket(this, 'MyEncryptedBucket', {
encryption: s3.BucketEncryption.KMS,
websiteIndexDocument: 'index.html'
});
- JavaScript
-
new s3.Bucket(this, 'MyEncryptedBucket', {
encryption: s3.BucketEncryption.KMS,
websiteIndexDocument: 'index.html'
});
- Python
-
s3.Bucket(self, "MyEncryptedBucket", encryption=s3.BucketEncryption.KMS,
website_index_document="index.html")
- Java
-
Bucket.Builder.create(this, "MyEncryptedBucket")
.encryption(BucketEncryption.KMS_MANAGED)
.websiteIndexDocument("index.html").build();
- C#
-
new Bucket(this, "MyEncryptedBucket", new BucketProps
{
Encryption = BucketEncryption.KMS_MANAGED,
WebsiteIndexDocument = "index.html"
});
- Go
-
awss3.NewBucket(stack, jsii.String("MyEncryptedBucket"), &awss3.BucketProps{
Encryption: awss3.BucketEncryption_KMS,
WebsiteIndexDocument: jsii.String("index.html"),
})
AWS constructs are designed around the concept of "sensible defaults." Most constructs have a minimal
required configuration, enabling you to quickly get started while also providing full control over the configuration
when you need it.
Interacting with constructs
Constructs are classes that extend the base Construct class. After you
instantiate a construct, the construct object exposes a set of methods and properties that let
you interact with the construct and pass it around as a reference to other parts of the
system.
The AWS CDK framework doesn't put any restrictions on the APIs of constructs. Authors can
define any API they want. However, the AWS constructs that are included with the AWS
Construct Library, such as s3.Bucket
, follow guidelines and common patterns. This
provides a consistent experience across all AWS resources.
Most AWS constructs have a set of grant
methods that you can use to grant AWS Identity and Access Management (IAM) permissions on that construct to a
principal. The following example grants the IAM group data-science
permission
to read from the Amazon S3 bucket raw-data
.
- TypeScript
-
const rawData = new s3.Bucket(this, 'raw-data');
const dataScience = new iam.Group(this, 'data-science');
rawData.grantRead(dataScience);
- JavaScript
-
const rawData = new s3.Bucket(this, 'raw-data');
const dataScience = new iam.Group(this, 'data-science');
rawData.grantRead(dataScience);
- Python
-
raw_data = s3.Bucket(self, 'raw-data')
data_science = iam.Group(self, 'data-science')
raw_data.grant_read(data_science)
- Java
-
Bucket rawData = new Bucket(this, "raw-data");
Group dataScience = new Group(this, "data-science");
rawData.grantRead(dataScience);
- C#
-
var rawData = new Bucket(this, "raw-data");
var dataScience = new Group(this, "data-science");
rawData.GrantRead(dataScience);
- Go
-
rawData := awss3.NewBucket(stack, jsii.String("raw-data"), nil)
dataScience := awsiam.NewGroup(stack, jsii.String("data-science"), nil)
rawData.GrantRead(dataScience, nil)
Another common pattern is for AWS constructs to set one of the resource's attributes
from data supplied elsewhere. Attributes can include Amazon Resource Names (ARNs), names, or
URLs.
The following code defines an AWS Lambda function and associates it with an Amazon Simple Queue Service
(Amazon SQS) queue through the queue's URL in an environment variable.
- TypeScript
-
const jobsQueue = new sqs.Queue(this, 'jobs');
const createJobLambda = new lambda.Function(this, 'create-job', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('./create-job-lambda-code'),
environment: {
QUEUE_URL: jobsQueue.queueUrl
}
});
- JavaScript
-
const jobsQueue = new sqs.Queue(this, 'jobs');
const createJobLambda = new lambda.Function(this, 'create-job', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('./create-job-lambda-code'),
environment: {
QUEUE_URL: jobsQueue.queueUrl
}
});
- Python
-
jobs_queue = sqs.Queue(self, "jobs")
create_job_lambda = lambda_.Function(self, "create-job",
runtime=lambda_.Runtime.NODEJS_18_X,
handler="index.handler",
code=lambda_.Code.from_asset("./create-job-lambda-code"),
environment=dict(
QUEUE_URL=jobs_queue.queue_url
)
)
- Java
-
final Queue jobsQueue = new Queue(this, "jobs");
Function createJobLambda = Function.Builder.create(this, "create-job")
.handler("index.handler")
.code(Code.fromAsset("./create-job-lambda-code"))
.environment(java.util.Map.of( // Map.of is Java 9 or later
"QUEUE_URL", jobsQueue.getQueueUrl())
.build();
- C#
-
var jobsQueue = new Queue(this, "jobs");
var createJobLambda = new Function(this, "create-job", new FunctionProps
{
Runtime = Runtime.NODEJS_18_X,
Handler = "index.handler",
Code = Code.FromAsset(@".\create-job-lambda-code"),
Environment = new Dictionary<string, string>
{
["QUEUE_URL"] = jobsQueue.QueueUrl
}
});
- Go
-
createJobLambda := awslambda.NewFunction(stack, jsii.String("create-job"), &awslambda.FunctionProps{
Runtime: awslambda.Runtime_NODEJS_18_X(),
Handler: jsii.String("index.handler"),
Code: awslambda.Code_FromAsset(jsii.String(".\\create-job-lambda-code"), nil),
Environment: &map[string]*string{
"QUEUE_URL": jsii.String(*jobsQueue.QueueUrl()),
},
})
For information about the most common API patterns in the AWS Construct Library, see
Resources.
Writing your own constructs
In addition to using existing constructs like s3.Bucket
, you can also write
your own constructs, and then anyone can use them in their apps. All constructs are equal in
the AWS CDK. An AWS CDK construct (such as s3.Bucket
or sns.Topic
)
behaves 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 also behave 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.
The following example shows how to declare a construct that represents an Amazon S3 bucket. The
S3 bucket sends an Amazon Simple Notification Service (Amazon SNS) notification every time someone uploads a file into
it.
- TypeScript
-
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 });
}
}
- JavaScript
-
class NotifyingBucket extends Construct {
constructor(scope, id, props = {}) {
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 });
}
}
module.exports = { NotifyingBucket }
- Python
-
class NotifyingBucket(Construct):
def __init__(self, scope: Construct, id: str, *, prefix=None):
super().__init__(scope, id)
bucket = s3.Bucket(self, "bucket")
topic = sns.Topic(self, "topic")
bucket.add_object_created_notification(s3notify.SnsDestination(topic),
s3.NotificationKeyFilter(prefix=prefix))
- Java
-
public class NotifyingBucket extends Construct {
public NotifyingBucket(final Construct scope, final String id) {
this(scope, id, null, null);
}
public NotifyingBucket(final Construct scope, final String id, final BucketProps props) {
this(scope, id, props, null);
}
public NotifyingBucket(final Construct scope, final String id, final String prefix) {
this(scope, id, null, prefix);
}
public NotifyingBucket(final Construct scope, final String id, final BucketProps props, final String prefix) {
super(scope, id);
Bucket bucket = new Bucket(this, "bucket");
Topic topic = new Topic(this, "topic");
if (prefix != null)
bucket.addObjectCreatedNotification(new SnsDestination(topic),
NotificationKeyFilter.builder().prefix(prefix).build());
}
}
- C#
-
public class NotifyingBucketProps : BucketProps
{
public string Prefix { get; set; }
}
public class NotifyingBucket : Construct
{
public NotifyingBucket(Construct scope, string id, NotifyingBucketProps props = null) : base(scope, id)
{
var bucket = new Bucket(this, "bucket");
var topic = new Topic(this, "topic");
bucket.AddObjectCreatedNotification(new SnsDestination(topic), new NotificationKeyFilter
{
Prefix = props?.Prefix
});
}
}
- Go
-
type NotifyingBucketProps struct {
awss3.BucketProps
Prefix *string
}
func NewNotifyingBucket(scope constructs.Construct, id *string, props *NotifyingBucketProps) awss3.Bucket {
var bucket awss3.Bucket
if props == nil {
bucket = awss3.NewBucket(scope, jsii.String(*id+"Bucket"), nil)
} else {
bucket = awss3.NewBucket(scope, jsii.String(*id+"Bucket"), &props.BucketProps)
}
topic := awssns.NewTopic(scope, jsii.String(*id+"Topic"), nil)
if props == nil {
bucket.AddObjectCreatedNotification(awss3notifications.NewSnsDestination(topic))
} else {
bucket.AddObjectCreatedNotification(awss3notifications.NewSnsDestination(topic), &awss3.NotificationKeyFilter{
Prefix: props.Prefix,
})
}
return bucket
}
Our NotifyingBucket
construct inherits not from Bucket
but
rather from Construct
. We are using composition, not inheritance, to bundle an
Amazon S3 bucket and an Amazon SNS topic together. In general, composition is preferred over
inheritance when developing AWS CDK constructs.
The NotifyingBucket
constructor has a typical construct signature: scope
,
id
, and props
. The last argument, props
, is optional (gets the default value
{}
) because all props are optional. (The base Construct
class does not take a
props
argument.) You could define an instance of this construct in your app without props
,
for example:
- TypeScript
-
new NotifyingBucket(this, 'MyNotifyingBucket');
- JavaScript
-
new NotifyingBucket(this, 'MyNotifyingBucket');
- Python
-
NotifyingBucket(self, "MyNotifyingBucket")
- Java
-
new NotifyingBucket(this, "MyNotifyingBucket");
- C#
-
new NotifyingBucket(this, "MyNotifyingBucket");
- Go
-
NewNotifyingBucket(stack, jsii.String("MyNotifyingBucket"), nil)
Or you could use props
(in Java, an additional parameter) to specify the path
prefix to filter on, for example:
- TypeScript
-
new NotifyingBucket(this, 'MyNotifyingBucket', { prefix: 'images/' });
- JavaScript
-
new NotifyingBucket(this, 'MyNotifyingBucket', { prefix: 'images/' });
- Python
-
NotifyingBucket(self, "MyNotifyingBucket", prefix="images/")
- Java
-
new NotifyingBucket(this, "MyNotifyingBucket", "/images");
- C#
-
new NotifyingBucket(this, "MyNotifyingBucket", new NotifyingBucketProps
{
Prefix = "/images"
});
- Go
-
NewNotifyingBucket(stack, jsii.String("MyNotifyingBucket"), &NotifyingBucketProps{
Prefix: jsii.String("images/"),
})
Typically, you would also want to expose some properties or methods on your constructs.
It's not very useful to have a topic hidden behind your construct, because users of your
construct aren't able to subscribe to it. Adding a topic
property lets consumers
access the inner topic, as shown in the following example:
- TypeScript
-
export class NotifyingBucket extends Construct {
public readonly topic: sns.Topic;
constructor(scope: Construct, id: string, props: NotifyingBucketProps) {
super(scope, id);
const bucket = new s3.Bucket(this, 'bucket');
this.topic = new sns.Topic(this, 'topic');
bucket.addObjectCreatedNotification(new s3notify.SnsDestination(this.topic), { prefix: props.prefix });
}
}
- JavaScript
-
class NotifyingBucket extends Construct {
constructor(scope, id, props) {
super(scope, id);
const bucket = new s3.Bucket(this, 'bucket');
this.topic = new sns.Topic(this, 'topic');
bucket.addObjectCreatedNotification(new s3notify.SnsDestination(this.topic), { prefix: props.prefix });
}
}
module.exports = { NotifyingBucket };
- Python
-
class NotifyingBucket(Construct):
def __init__(self, scope: Construct, id: str, *, prefix=None, **kwargs):
super().__init__(scope, id)
bucket = s3.Bucket(self, "bucket")
self.topic = sns.Topic(self, "topic")
bucket.add_object_created_notification(s3notify.SnsDestination(self.topic),
s3.NotificationKeyFilter(prefix=prefix))
- Java
-
public class NotifyingBucket extends Construct {
public Topic topic = null;
public NotifyingBucket(final Construct scope, final String id) {
this(scope, id, null, null);
}
public NotifyingBucket(final Construct scope, final String id, final BucketProps props) {
this(scope, id, props, null);
}
public NotifyingBucket(final Construct scope, final String id, final String prefix) {
this(scope, id, null, prefix);
}
public NotifyingBucket(final Construct scope, final String id, final BucketProps props, final String prefix) {
super(scope, id);
Bucket bucket = new Bucket(this, "bucket");
topic = new Topic(this, "topic");
if (prefix != null)
bucket.addObjectCreatedNotification(new SnsDestination(topic),
NotificationKeyFilter.builder().prefix(prefix).build());
}
}
- C#
-
public class NotifyingBucket : Construct
{
public readonly Topic topic;
public NotifyingBucket(Construct scope, string id, NotifyingBucketProps props = null) : base(scope, id)
{
var bucket = new Bucket(this, "bucket");
topic = new Topic(this, "topic");
bucket.AddObjectCreatedNotification(new SnsDestination(topic), new NotificationKeyFilter
{
Prefix = props?.Prefix
});
}
}
- Go
-
To do this in Go, we'll need a little extra plumbing. Our original
NewNotifyingBucket
function returned an awss3.Bucket
.
We'll need to extend Bucket
to include a topic
member by
creating a NotifyingBucket
struct. Our function will then return this
type.
type NotifyingBucket struct {
awss3.Bucket
topic awssns.Topic
}
func NewNotifyingBucket(scope constructs.Construct, id *string, props *NotifyingBucketProps) NotifyingBucket {
var bucket awss3.Bucket
if props == nil {
bucket = awss3.NewBucket(scope, jsii.String(*id+"Bucket"), nil)
} else {
bucket = awss3.NewBucket(scope, jsii.String(*id+"Bucket"), &props.BucketProps)
}
topic := awssns.NewTopic(scope, jsii.String(*id+"Topic"), nil)
if props == nil {
bucket.AddObjectCreatedNotification(awss3notifications.NewSnsDestination(topic))
} else {
bucket.AddObjectCreatedNotification(awss3notifications.NewSnsDestination(topic), &awss3.NotificationKeyFilter{
Prefix: props.Prefix,
})
}
var nbucket NotifyingBucket
nbucket.Bucket = bucket
nbucket.topic = topic
return nbucket
}
Now, consumers can subscribe to the topic, for example:
- TypeScript
-
const queue = new sqs.Queue(this, 'NewImagesQueue');
const images = new NotifyingBucket(this, '/images');
images.topic.addSubscription(new sns_sub.SqsSubscription(queue));
- JavaScript
-
const queue = new sqs.Queue(this, 'NewImagesQueue');
const images = new NotifyingBucket(this, '/images');
images.topic.addSubscription(new sns_sub.SqsSubscription(queue));
- Python
-
queue = sqs.Queue(self, "NewImagesQueue")
images = NotifyingBucket(self, prefix="Images")
images.topic.add_subscription(sns_sub.SqsSubscription(queue))
- Java
-
NotifyingBucket images = new NotifyingBucket(this, "MyNotifyingBucket", "/images");
images.topic.addSubscription(new SqsSubscription(queue));
- C#
-
var queue = new Queue(this, "NewImagesQueue");
var images = new NotifyingBucket(this, "MyNotifyingBucket", new NotifyingBucketProps
{
Prefix = "/images"
});
images.topic.AddSubscription(new SqsSubscription(queue));
- Go
-
queue := awssqs.NewQueue(stack, jsii.String("NewImagesQueue"), nil)
images := NewNotifyingBucket(stack, jsii.String("MyNotifyingBucket"), &NotifyingBucketProps{
Prefix: jsii.String("/images"),
})
images.topic.AddSubscription(awssnssubscriptions.NewSqsSubscription(queue, nil))
The construct tree
As we've already seen, in AWS CDK apps, you define constructs "inside" other constructs
using the scope
argument passed to every construct. In this way, an AWS CDK app
defines a hierarchy of constructs known as the construct tree.
The root of this tree is your app—that is, an instance of the App
class. Within the app, you instantiate one or more stacks. Within stacks, you instantiate
either AWS CloudFormation resources or higher-level constructs, which may themselves instantiate resources
or other constructs, and so on down the tree.
Constructs are always explicitly defined within the scope of another
construct, so there is no doubt about the relationships between constructs. Almost always, you
should pass this
(in Python, self
) as the scope, indicating that the
new construct is a child of the current construct. The intended pattern is that you derive
your construct from Construct
, then
instantiate the constructs it uses in its constructor.
Passing the scope explicitly allows each construct to add itself to the tree, with this
behavior entirely contained within the Construct
base class.
It works the same way in every language supported by the AWS CDK and does not require
introspection or other "magic."
Technically, it's possible to pass some scope other than this
when
instantiating a construct. You can add constructs anywhere in the tree, or even in another
stack in the same app. For example, you could write a mixin-style function that adds
constructs to a scope passed in as an argument. The practical difficulty here is that you
can't easily ensure that the IDs you choose for your constructs are unique within someone
else's scope. The practice also makes your code more difficult to understand, maintain, and
reuse. It is almost always better to find a way to express your intent without resorting to
abusing the scope
argument.
The AWS CDK uses the IDs of all constructs in the path from the tree's root to each child
construct to generate the unique IDs required by AWS CloudFormation. This approach means that construct IDs
only need to be unique within their scope, rather than within the entire stack as in native
AWS CloudFormation. However, if you move a construct to a different scope, its generated stack-unique ID
changes, and AWS CloudFormation won't consider it the same resource.
The construct tree is separate from the constructs that you define in your AWS CDK code.
However, it's accessible through any construct's node
attribute, which is a
reference to the node that represents that construct in the tree. Each node is a Node
instance, the attributes of which provide access to the tree's root and to the node's parent
scopes and children.
-
node.children
– The direct children of the construct.
-
node.id
– The identifier of the construct within its scope.
-
node.path
– The full path of the construct including the IDs of all
of its parents.
-
node.root
– The root of the construct tree (the app).
-
node.scope
– The scope (parent) of the construct, or undefined if
the node is the root.
-
node.scopes
– All parents of the construct, up to the root.
-
node.uniqueId
– The unique alphanumeric identifier for this
construct within the tree (by default, generated from node.path
and a
hash).
The construct tree defines an implicit order in which constructs are synthesized to
resources in the final AWS CloudFormation template. Where one resource must be created before another,
AWS CloudFormation or the AWS Construct Library generally infers the dependency. They then make sure that
the resources are created in the right order.
You can also add an explicit dependency between two nodes by using
node.addDependency()
. For more information, see Dependencies in the
AWS CDK API Reference.
The AWS CDK provides a simple way to visit every node in the construct tree and perform an
operation on each one. For more information, see Aspects.