Intro to AWS CDK

EJEJ
4 min read

This is originally written in 2023 in my old blog. I will update it post publish.

From the documentation, AWS CDK is described as a framework for defining cloud infrastructure in code and provisioning it through AWS CloudFormation.

As a visual person, I found this diagram very helpful to understand what AWS CDK does:

Ignoring the new terminologies such as Stack(s) and Construct, we can see that AWS CDK (the big orange box) allow us to write code (in TypeScript, JavaScript, etc) to produce CloudFormation template.

The AWS CDK documentation is very comprehensive and there are also many great resources to learn that is being added and updated regularly.


In this post, I will share what I learned so far about AWS CDK and what is great, what is promising, and what is not so great about it in relation to how we use AWS.

What is Great

Less verbosity

Defining AWS resources using AWS CDK is much less verbose. The side-by-side comparison below shows DynamoDB table definition

AWS CDK

new Table(this, 'mockserver-persistence', {
  tableName: `${EnvironmentName}-mocks-persistence`,
  timeToLiveAttribute: 'TTL',
  partitionKey: {
    name: 'primaryPk',
    type: AttributeType.STRING
  },
  sortKey: {
    name: 'primarySk',
    type: AttributeType.STRING
  },
  billingMode: BillingMode.PAY_PER_REQUEST,
})

AWS CloudFormation

MocksPersistenceTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub '${EnvironmentName}-mocks-persistence'
      TimeToLiveSpecification:
        AttributeName: TTL
        Enabled: true
      AttributeDefinitions:
        - AttributeName: primaryPk
          AttributeType: S
        - AttributeName: primarySk
          AttributeType: S
      KeySchema:
        - AttributeName: primaryPk
          KeyType: HASH
        - AttributeName: primarySk
          KeyType: RANGE
      BillingMode: PAY_PER_REQUEST

Less Complexity

The verbosity will be even more pronounced when we add other resources and make sure they all have the right access and permissions.

AWS CDK

const table = new Table(this, 'mockserver-persistence', {...
...
const lambda = new Function(...)
...
table.grantReadData(lambda)

AWS CloudFormation

Type: AWS::IAM::Role
  ... 10 +lines

Type: AWS::IAM::Policy
  ... 10+ lines

From the above, we can see that the complexity of CloudFormation was hidden from us.

Access to Low Level CloudFormation Resources

In real life, the abstraction that is provided by AWS CDK might not fit our needs as we might have done some wrangling to our CloudFormation template for our need.

In these scenarios, we can use the CDK’s L1 (layer 1) constructs which is directly represent CloudFormation resources (these are CDK classes with “Cfn” prefix)

An example:

new CfnTable(this, 'id', {
  tableName: `${EnvironmentName}-mocks-persistence`,
  timeToLiveSpecification: {
    attributeName: 'TTL',
    enabled: true
  },
  attributeDefinitions: [
    {
      attributeName: 'primaryPk',
      attributeType: AttributeType.STRING
    },
    {
      attributeName: 'primarySk',
      attributeType: AttributeType.STRING
    }
  ],
  keySchema: [
    {
      attributeName: 'primaryPk',
      keyType: 'HASH'
    },
    {
      attributeName: 'primarySk',
      keyType: 'RANGE'
    }
  ],
  billingMode: BillingMode.PAY_PER_REQUEST
})

Another option is to use the CfnInclude class to include our CloudFormation resources either .yaml or .json (recommended) file.

An example:

CfnInclude(this, 'Template', {templateFile: 'template.yaml'});

The benefit of this approach is that it will preserve the resources’s original logical IDs.

Constructs - NodeJsFunction

There are many constructs that make life easier when working with AWS. Once such construct is NodeJsFunction that allow us to write Lambda function in TypeScript which will be bundled using esbuild.

This will the work of transpiling our TypeScript to JavaScript and bundling it with required dependencies will be taken care of for us.

What is Promising

One of the promise of AWS CDK is that our infrastructure code (written in TypeScript, etc) can be as testable as any other code we write.

There are 2 types of tests which are Fine-grained assertions and Snapshot tests.

Fine-grained assertions are most common and useful to catch regressions which is the main purpose of writing infrastructure tests.

An example:

// assemble
... boilerplace code to get template object to assert ...

// assert
template.hasResourceProperties('AWS::DynamoDB::Table', Match.objectLike({
  BillingMode: 'PAY_PER_REQUEST',
  TimeToLiveSpecification: { 
    AttributeName: 'TTL', 
    Enabled: true,
  },
  KeySchema: [
    { AttributeName: 'primaryPk', KeyType: 'HASH' }, 
    { AttributeName: 'primarySk', KeyType: 'RANGE' },
  ],
  AttributeDefinitions: [
    { AttributeName: 'primaryPk', AttributeType: 'S' }, 
    { AttributeName: 'primarySk', AttributeType: 'S' },
  ]
}))

While great and useful, after writing tests for awhile, it feels that the tests is just a representation of the CloudFormation template so good knowledge of CloudFormation is necessary.


More to come:

  • Live Coding AWS CDK app

  • Migration of our Mocks API (replacement of MockLab) to use AWS CDK

0
Subscribe to my newsletter

Read articles from EJ directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

EJ
EJ