Shorter AWS CDK Logical IDs

Shorter AWS CDK Logical IDs - AWS CDK

Let’s take a typical CDK stack that defines one or more “modules” that represent the infrastructure that backs various features of the app:

export class AppStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id, props);
new Auth(this, 'Auth');
// Other modules (constructs) here
}
}

For the sake of example, we are only creating an Auth module, that abstracts away a lot of the moving parts related to user registration, authentication, forgot password functionalities.

Here’s what Auth.ts looks like:

/**
* Auth module. Handles login, registration, forgot password
*/
class Auth extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
new Login(this, 'Login');
}
}

Again, for the sake of simple examples, we’ll only define the Login related feature, which is backed by a simple Lambda (using the NodejsFunction but we might as well use the `new Lambda()` construct just fine here).

/**
* Users module. Defines based user related functionality
*/
class Login extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
// Accepts a username + password and returns an access_token
new NodejsFunction(this, 'Lambda-login', {
entry: path.resolve(__dirname, 'login.ts'),
});
}
}

(The contents of login.ts are out of scope for the purposes of this article)

Now, if we deploy our stack and inspect the name of the generated Lambda function, it usually looks something like this:

App-AuthLoginLambdalogin7B7EBC2E-o4TMobMBEldl

The same would apply if we create an S3 bucket or any other resource at that level. The resource name is just terrible and hard to read because it includes the full hierarchy of construct IDs up until that construct that needs a name.

This is okay for trivial use cases, but as your CDK project grows in complexity (and hierarchically) you will eventually run into trouble since Lambda names have a technical limitation on the maximum amount of characters in their name.

For this reason, CDK will eventually start trimming the last parts of the construct IDs hierarchy, which includes the most useful information – the Lambda ID itself (in our case, that would be Lambda-login).

How do we make our Logical IDs make sense, for Lambda, DynamoDB, S3, or any other resource we create deep in the CDK project’s hierarchy?

Luckily, I found this hidden gem within the AWS CDK source code. It’s a piece of code that reveals a detail about the internal mechanisms of Logical ID generating by AWS CDK.

Basically, by default CDK will just take the full hierarchy of IDs of constructs and append them in a long string. If the underlying service has a limitation on the total number of characters (like Lambda does), CDK will simply trim up to a predefined limit and work with that result.

However, we have a simple trick to control this generated string, that is used as a Logical ID.

Pro Tip of the day: Try to use “Default” as much as possible as a Construct ID.

Whenever you use “Default” as a construct ID (second parameter of every Construct), CDK will try to REMOVE that name from the final string that represents the Logical ID of the resource, essentially, collapsing a lot of unnecessary words from the generated Logical ID.

Here’s what the Lambda name would look like without any changes:

App-AuthLoginLambdalogin7B7EBC2E-o4TMobMBEldl

And here’s what the name looks like if we used “Default” as a construct ID for Auth.ts and Login.ts:

App-LambdaloginB0F7BEC6-ui9Xx4DvrjUf

So we have only the stack name and the final Lambda construct ID.

Of course, there are limitations to keep in mind:

  • Don’t use this if you WANT to keep this contextual information in the Logical ID of the final resource, e.g. you have 2 different Login lambdas that live in different places in the hierarchy and you want an easy way to distinguish them.
  • You can’t have more than one resource with an ID of “Default” within the same construct, but you do follow the “single responsibility” principle in your constructs anyway? Ideally, a construct should only take care of creating one main thing (and maybe a few smaller things related to that main thing – e.g. IAM roles, CloudWatch Log Groups).

Need help? Get in touch