AWS Cloud Development Kit: Simple Approach to Infrastructure-as-Code

While Terraform is the de facto standard for writing infrastructure-as-code (IaC), a few years ago, a new breed of IaC tools that allow you to define infrastructure in various object oriented programming languages started gaining traction. It started with Pulumi, and now we have the CDKTF and the AWS Cloud Development Kit (CDK).

These tools allow you to define infrastructure using a language that you are likely familiar with if you are a developer. While they support multiple languages, there is generally one that has better support and in the case of most of these tools, that language is typescript.

There are some benefits in being able to define your infrastructure in an object oriented programming language as we will see. As with anything, there are plenty of cons as well and it really depends on what suits your tech stack and your organization.

Creating a lambda with SQS: Terraform vs AWS CDK

One common pattern in serverless architectures is having a lambda read from an SQS queue. This allows you to process as many messages as allowed concurrent executions in your AWS account. You can forget about having to scale your consumers, since for every message in the queue a lambda function will be triggered.

Let’s see how much code writing this requires in Terraform vs AWS CDK


					resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"

  assume_role_policy = <<EOF
  "Version": "2012-10-17",
  "Statement": [
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": ""
      "Effect": "Allow",
      "Sid": ""

resource "aws_lambda_function" "test_lambda" {
  filename         = ""
  function_name    = "lambda_function_name"
  role             = "${aws_iam_role.iam_for_lambda.arn}"
  handler          = "exports.test"
  source_code_hash = "${base64sha256(file(""))}"
  runtime          = "nodejs4.3"

  environment {
    variables = {
      foo = "bar"

resource "aws_lambda_event_source_mapping" "event_source_mapping" {
  event_source_arn = "${var.terraform_queue_arn}"
  enabled          = true
  function_name    = "${aws_lambda_function.test_lambda.arn}"
  batch_size       = 1

resource "aws_sqs_queue" "terraform_queue" {
  name                      = "terraform-example-queue"



					import * as lambda from 'aws-cdk-lib/aws-lambda';
import {SqsEventSource} from 'aws-cdk-lib/aws-lambda-event-sources';
import {NodejsFunction} from 'aws-cdk-lib/aws-lambda-nodejs';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as cdk from 'aws-cdk-lib';
import * as path from 'path';

export class LambdaSQSStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
   const queue = new sqs.Queue(this, 'sqs-queue');

    const myLambda = new NodejsFunction(this, 'my-lambda', {
      memorySize: 1024,
      timeout: cdk.Duration.seconds(5),
      runtime: lambda.Runtime.NODEJS_14_X,
      handler: 'main',
      entry: path.join(__dirname, `/../src/my-lambda/index.ts`),
      new SqsEventSource(queue, {
        batchSize: 10,

So we already see some benefit in terms of lines of code written. Let’s add a Amazon DynamoDB table that our lambda function will write to.


					resource "aws_dynamodb_table" "ddbtable" {
  name             = "myDB"
  hash_key         = "id"
  billing_mode   = "PROVISIONED"
  read_capacity  = 5
  write_capacity = 5
  attribute {
    name = "id"
    type = "S"

resource "aws_iam_role_policy" "lambda_policy" {
  name = "lambda_policy"
  role =

  policy = <<POLICY
  "Version": "2012-10-17",
    "Effect": "Allow",
    "Action": [
    "Resource": "arn:aws:dynamodb:us-east-1:987456321456:table/myDB"



					 const table = new dynamodb.Table(this, id, {
      billingMode: dynamodb.BillingMode.PROVISIONED,
      readCapacity: 5,
      writeCapacity: 5,
      partitionKey: {name: 'id', type: dynamodb.AttributeType.STRING},
      sortKey: {name: 'createdAt', type: dynamodb.AttributeType.NUMBER}



One of the main benefits of using an object oriented programming language is that our resources are actually objects, thus you can call methods on them, something that is not possible in Terraform due to the nature of the Hashicorp Configuration Language (HCL), which behaves more like a Domain Specific Language (DSL) and not a real programming language. The ability of resources having both methods and properties makes code easier to understand, since you can use properties as means to just read data from your resources and use methods for anything that would change the state of that resource.

Your constructs can also implement interfaces that are part of the AWS CDK. In this case our lambda function implements the IGrantable interface, which allows you to pass the lambda as an argument to the “grantReadWrite” method. You can also implement this interface in your custom constructs to simplify granting permissions to any children resources of the construct. This saves a lot of boilerplate code and also makes the code’s intent much clearer with less lines.


Nobody is going to migrate a whole codebase from Terraform to AWS CDK just because you no longer have to memorize every IAM action. This is just a small example of how it makes sense to use an object oriented language to write infrastructure. In my experience, the AWS CDK proves particularly convenient when you want to give more autonomy to development teams. It is more likely that they will adopt a programming language that they already know and are familiar with than an HCL.

Share this article

Leave a comment


Share this article


Join Thousands of DevOps & Cloud Professionals. Sign up for our newsletter for updated information, insight and promotion.