CDK constructs aren’t just abstractions; they’re a tiered system of control, with L3 constructs often acting as opinionated, pre-configured solutions that hide underlying complexity.
Let’s see this in action. Imagine you want to deploy a simple S3 bucket with versioning and logging enabled.
Here’s how you’d do it with L1, L2, and L3 constructs:
L1 (Low-Level):
from aws_cdk import aws_s3 as s3
# This is a direct translation of the CloudFormation resource
bucket_l1 = s3.CfnBucket(
self, "MyL1Bucket",
versioning_configuration=s3.CfnBucket.VersioningConfigurationProperty(
status="Enabled"
),
logging_configuration=s3.CfnBucket.LoggingConfigurationProperty(
log_bucket="my-log-bucket-name", # You'd need to create this separately or define it here
log_prefix="my-bucket-logs/"
)
)
This is essentially a direct mapping to the AWS CloudFormation AWS::S3::Bucket resource. You’re specifying every single property as defined by the CloudFormation schema. It’s verbose and requires deep knowledge of the underlying resource.
L2 (Mid-Level):
from aws_cdk import aws_s3 as s3
# This provides a more idiomatic and object-oriented interface
bucket_l2 = s3.Bucket(
self, "MyL2Bucket",
versioned=True,
logging_bucket_name="my-log-bucket-name" # Still requires the log bucket name
)
The L2 construct, s3.Bucket, abstracts away much of the CloudFormation detail. versioned=True is a clear, readable boolean. While logging_bucket_name is still there, the overall structure is cleaner and more aligned with typical programming paradigms. It handles default configurations and provides methods for further customization.
L3 (High-Level):
from aws_cdk import aws_s3 as s3
from aws_cdk import aws_s3_assets as s3_assets # Example of a common L3 pattern
# This is an opinionated, pre-configured solution for a specific use case
# For example, a bucket designed to host static website assets
website_bucket = s3_assets.Asset(
self, "MyWebsiteAsset",
path="./my-website-content"
)
Here, s3_assets.Asset isn’t just about creating an S3 bucket; it’s about deploying website assets. It includes the logic to create a bucket, configure it for static website hosting (often enabling versioning and logging by default), and even handles uploading the content from a local path. This is a complete solution for a common pattern, abstracting away the creation of the bucket, the upload process, and the necessary configurations. You’re not thinking about CfnBucket properties; you’re thinking about deploying a website.
The core problem CDK addresses is the boilerplate and repetitive nature of defining cloud infrastructure, especially when common patterns emerge. L1, L2, and L3 constructs provide a spectrum of abstraction to manage this.
- L1 constructs (
Cfn...): These are direct, one-to-one mappings of CloudFormation resources. They offer the most granular control but are also the most verbose and least "CDK-like." You use them when you need to access a specific property not yet exposed by higher-level constructs or when you’re migrating existing CloudFormation. - L2 constructs (e.g.,
s3.Bucket,ec2.Vpc): These are the workhorses of CDK. They wrap L1 constructs, providing more idiomatic, object-oriented APIs, sensible defaults, and often methods for common operations. They aim for a balance between control and ease of use. For example, an L2Vpcconstruct automatically provisions multiple Availability Zones, public and private subnets, an Internet Gateway, and NAT Gateways, all configured according to AWS best practices. - L3 constructs (e.g.,
s3_assets.Asset,ecs_patterns.ApplicationLoadBalancedFargateService): These are the highest level of abstraction. They are opinionated, pre-packaged solutions for common architectural patterns. They often combine multiple L2 and L1 constructs to deliver a complete feature. Anecs_patterns.ApplicationLoadBalancedFargateServicemight create an ECS cluster, a Fargate service, an Application Load Balancer, target groups, listeners, and security groups – all from a single construct. You’re not configuring individual AWS resources; you’re deploying a "web service."
The beauty of CDK’s construct levels is that they build upon each other. An L2 construct is often composed of L1 constructs, and an L3 construct is usually a composition of L2 and L1 constructs. This allows for a clear hierarchy and the ability to "escape" to a lower level of abstraction when needed. If an L3 construct doesn’t quite meet your needs, you can often drop down to the L2 construct it uses, or even the underlying L1 if absolutely necessary.
Many developers new to CDK focus on L2 constructs, which is a great starting point. However, understanding L3 constructs is where you unlock significant productivity gains for common cloud patterns. They encapsulate best practices and reduce the cognitive load of assembling complex infrastructure. For instance, when provisioning a new EKS cluster, you might start with the eks.FargateCluster L3 construct, which handles the intricate setup of Fargate profiles, IAM roles, and VPC networking required for a serverless Kubernetes experience.
The common pitfall is treating all constructs as equal. L1s are for control, L2s for idiomatic use, and L3s for rapid deployment of established patterns.
As you become more comfortable, you’ll start seeing how L3 constructs are built from L2s, and L2s from L1s, which will unlock your ability to create your own custom L3-like constructs tailored to your organization’s specific needs.
The next step after mastering construct levels is understanding how to define and use custom constructs, bridging the gap between CDK’s built-in abstractions and your team’s unique requirements.