CDK: how to customize 3rd-party L3 constructs

Yan Cui

I help clients go faster for less using serverless technologies.

This article is brought to you by

Don’t reinvent the patterns. Catalyst gives you consistent APIs for messaging, data, and workflow with key microservice patterns like circuit-breakers and retries for free.

Try the Catalyst beta

If you’re using CDK, you should use L3 constructs to encapsulate common patterns and best practices in your architecture. The ability to create reusable, higher-level components is where CDK shines over other IaC tools such as SAM and Serverless Framework.

However, as Matt Bonig pointed out [1], sharing these reusable L3 constructs across organizations is difficult. Because every organization has its own quirks and requirements. As a construct author, it’s impossible to predict all these variations ahead of time.

As a consumer of 3rd-party L3 constructs, you often face the opposite problem – how to customize the resources created by these L3 constructs.

For example, you might find a third-party L3 construct that does most of what you want, but its Lambda functions are not configured to use a VPC. What do you do then?

You can clone the code and create your own construct, but that adds maintenance overhead to keep pace with changes to the original source code.

A better approach is to customize the resources created by the L3 construct with an Aspect [2].

Introduction to CDK Aspects

CDK puts all of your resources into a construct tree. The CDK app is at the root of the tree, followed by the stacks and their constructs.

You can traverse this tree using the methods on the Node class.

However, CDK has a built-in mechanism for traversing this tree through Aspects. To write your own Aspect, you create a class that implements this visit function:

visit(node: IConstruct): void

When an Aspect is applied to a node in the construct tree, CDK will traverse the node and its children and call your visit function on each.

Aspects.of(myConstruct).add(new MyAspect(...))

Customizing a 3rd-party L3 construct

For the aforementioned example, you can create an Aspect to customize the VPC settings on all Lambda functions it comes across.

const { aws_lambda } = require('aws-cdk-lib')

class LambdaVpcAspect {
  constructor(sgIds, subnetIds) {
    this.sgIds = sgIds
    this.subnetIds = subnetIds
  }

  visit(node) {
    if (node instanceof aws_lambda.Function) {
      const child = node.node.defaultChild
      child.vpcConfig = {
        securityGroupIds: this.sgIds,
        subnetIds: this.subnetIds
      }
    }
  }
}

module.exports = {
  LambdaVpcAspect,
}

When this Aspect is applied to an instance of the 3rd-party L3 construct:

Aspects.of(3rdPartyConstruct).add(new LambdaVpcAspect(mySgIds, mySubnetIds))

it will modify the vpcConfig of all the Lambda functions that are created by the L3 construct.

That’s it! An easy and effective way to customize 3rd-party L3 constructs in CDK.

Links

[1] AWS CDK: problem with creating reusable L3 constructs

[2] Official documentation on CDK Aspects

Whenever you’re ready, here are 3 ways I can help you:

  1. Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
  2. I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
  3. Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.