AWS cdk documentation change
Summary
Extensive rewrite of logical ID documentation section, adding detailed explanation of how AWS CDK generates logical IDs, heuristics for path components, and guidance on preserving logical IDs during refactoring using the 'Default' construct ID pattern.
Security assessment
This change is purely a documentation enhancement about logical ID generation and refactoring patterns. There is no evidence of addressing a security vulnerability, weakness, or incident. The content focuses on deployment stability and avoiding unintended resource replacements during code refactoring, which are operational concerns rather than security issues.
Diff
diff --git a/cdk/v2/guide/identifiers.md b/cdk/v2/guide/identifiers.md index 08f0334ba..9143c5cc9 100644 --- a//cdk/v2/guide/identifiers.md +++ b//cdk/v2/guide/identifiers.md @@ -264 +264,374 @@ C# -Unique IDs serve as the _logical identifiers_ (or _logical names_) of resources in the generated AWS CloudFormation templates for constructs that represent AWS resources. +When the AWS CDK synthesizes your app into an AWS CloudFormation template, it generates a _logical ID_ for each resource. AWS CloudFormation uses logical IDs to identify resources within a template and to track them across deployments. Understanding how logical IDs are generated helps you avoid unintended resource replacements when you refactor your CDK code. + +### How logical IDs are generated + +The AWS CDK generates logical IDs from the construct path by using the following algorithm: + + 1. Concatenate the path components from the construct tree, excluding the stack itself. + + 2. Apply heuristics to improve readability (see Logical ID path component heuristics). + + 3. Append an 8-character hash of the full path to ensure uniqueness. + + + + +The resulting format is: + + + <human-readable-portion><8-character-hash> + +For example, a VPC private subnet route table might produce the logical ID `VPCPrivateSubnet2RouteTable0A19E10E`. + +The following rules apply to logical ID generation: + + * The maximum length is 255 characters. The human-readable portion is capped at 240 characters. + + * The 8-character hash ensures that paths like `A/B/C` and `A/BC` — which concatenate to the same string — produce different logical IDs. + + * Resources that are direct children of the stack (single-component paths) use their name directly without a hash, as long as the name is 255 characters or fewer. + + + + +### Logical ID path component heuristics + +The AWS CDK applies the following heuristics to path components when it generates the human-readable portion of logical IDs. + +`Default` — removed entirely + + +If a path component is `Default`, the CDK removes it from both the human-readable portion and the hash input. This means that wrapping an existing construct inside a new construct — and naming the inner construct `Default` — produces the exact same logical ID as the original unwrapped construct. This is the key mechanism for safely refactoring flat code into higher-level constructs without changing deployed resource identities. + +`Resource` — hidden from human-readable portion only + + +If a path component is `Resource`, the CDK omits it from the human-readable portion but still includes it in the hash calculation. L1 (CloudFormation) constructs use `Resource` as their construct ID by convention. This keeps logical IDs shorter without losing uniqueness. + +Duplicate consecutive components — deduplicated + + +If the preceding path component name ends with the current component name, the CDK skips the current component. This prevents redundant repetition in logical IDs. + +### Use `Default` to preserve logical IDs when you refactor + +When you refactor a flat stack into higher-level constructs, you can use `Default` as the construct ID for the primary resource to preserve its logical ID. This prevents AWS CloudFormation from replacing the resource during deployment. + +The following example shows a stack with resources that are defined directly: + +###### Example + +TypeScript + + + + export class MyStack extends cdk.Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + new s3.Bucket(this, 'DataBucket'); + new lambda.Function(this, 'ProcessFunction', { /* ... */ }); + } + } + +JavaScript + + + + class MyStack extends cdk.Stack { + constructor(scope, id) { + super(scope, id); + new s3.Bucket(this, 'DataBucket'); + new lambda.Function(this, 'ProcessFunction', { /* ... */ }); + } + } + +Python + + + + from aws_cdk import ( + Stack, + aws_s3 as s3, + aws_lambda as _lambda, + ) + from constructs import Construct + + class MyStack(Stack): + def __init__(self, scope: Construct, id: str, **kwargs) -> None: + super().__init__(scope, id, **kwargs) + s3.Bucket(self, "DataBucket") + _lambda.Function(self, "ProcessFunction", # ... + ) + +Java + + + + package com.myorg; + + import software.constructs.Construct; + import software.amazon.awscdk.Stack; + import software.amazon.awscdk.StackProps; + import software.amazon.awscdk.services.s3.Bucket; + import software.amazon.awscdk.services.lambda.Function; + + public class MyStack extends Stack { + public MyStack(final Construct scope, final String id) { + this(scope, id, null); + } + + public MyStack(final Construct scope, final String id, final StackProps props) { + super(scope, id, props); + new Bucket(this, "DataBucket"); + Function.Builder.create(this, "ProcessFunction") + // ... + .build(); + } + } + +C# + + + + using Amazon.CDK; + using Constructs; + using Amazon.CDK.AWS.S3; + using Amazon.CDK.AWS.Lambda; + + namespace MyApp + { + public class MyStack : Stack + { + public MyStack(Construct scope, string id, StackProps props = null) : base(scope, id, props) + { + new Bucket(this, "DataBucket"); + new Function(this, "ProcessFunction", new FunctionProps + { + // ... + }); + } + } + } + +Go + + + + package main + + import ( + "github.com/aws/aws-cdk-go/awscdk/v2" + "github.com/aws/aws-cdk-go/awscdk/v2/awslambda" + "github.com/aws/aws-cdk-go/awscdk/v2/awss3" + "github.com/aws/constructs-go/constructs/v10" + "github.com/aws/jsii-runtime-go" + ) + + type MyStackProps struct { + awscdk.StackProps + } + + func NewMyStack(scope constructs.Construct, id string, props *MyStackProps) awscdk.Stack { + stack := awscdk.NewStack(scope, &id, &props.StackProps) + + awss3.NewBucket(stack, jsii.String("DataBucket"), &awss3.BucketProps{}) + awslambda.NewFunction(stack, jsii.String("ProcessFunction"), &awslambda.FunctionProps{ + // ... + }) + + return stack + } + +The bucket’s path is `MyStack/DataBucket/Resource`, which produces a logical ID of `DataBucket<hash>`. + +You can extract the bucket into a higher-level construct and preserve the same logical ID by naming the inner construct `Default`: + +###### Example + +TypeScript + + + + class DataPipeline extends Construct { + constructor(scope: Construct, id: string) { + super(scope, id);