When to Use AWS CDK Constructs vs Stacks

When to Use AWS CDK Constructs vs Stacks

AWS CDK Pro Tips

Are you confused about when to use a Construct vs a Stack when using the AWS CDK? They can be used interchangeably in a lot of situations. So when do you use one over the other? I could not find many resources on this topic so I decided to write about it!

☁️ Contents ☁️

Constructs

Constructs are the basic building blocks of the AWS CDK. They create "cloud components" and encapsulate everything needed to provision the component. Under the hood, Constructs are deconstructed into AWS CloudFormation templates. Almost anything you can do in AWS CloudFormation you can do with the AWS CDK!

A construct represents a single resource. For example, an Amazon S3 bucket or an Amazon EC2 server. AWS has also developed higher-level Constructs. Higher-level Constructs can be created to abstract the complexity of provisioning multiple AWS resources. AWS Elastic Container Service (ECS) and ecsPatterns are great examples of being able to create complicated cloud infrastructure in a handful of lines of code.

// provision a load balancers, ECR repository, ECS service and task using one Contstuct!
const loadBalancedEcsService = new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry('test'),
    environment: {
      TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value",
      TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value"
    },
  },
  desiredCount: 2,
});

Constructs communicate with each other. The AWS CDK team has done a great job of exposing attributes that allow you to create AWS CloudWatch Metrics, Alerts, and Dashboards.

Stacks

Each group of deployed resources in the AWS CDK is called a stack. These resources are directly or indirectly provisioned as a single unit. This allows you to create modular cloud infrastructure that is interchangeable.

A great example of this is grouping frontend and backend components into their own stacks. If you are building a backend for an application using AWS ECS you can encapsulate all the resources into one stack. If you decide you would like to migrate your infrastructure to serverless using AWS Lambda, you can easily spin up a new stack and tear down the old one once you are done.

Here is an example of a MetricsProps. This provisions several AWS CloudWatch metrics for a number of AWS ECS Fargate Services.

import * as cdk from '@aws-cdk/core';
import { Dashboard } from "@aws-cdk/aws-cloudwatch";
import { GraphWidget} from "@aws-cdk/aws-cloudwatch";

export interface MetricsProps extends cdk.StackProps {
    fargateService: ecs.FargateService[],
}

export class MetricsStack extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string, props: MetricsProps) {
        super(scope, id, props);

        // dashboard
        const dashboard = new Dashboard(this, `'dev-dashboard`, {
        });

        // ecs     
        const ecsCPU = new GraphWidget({
            width: 6,
            title: `ECS CPU Utilization`,
            left: props.fargateService.map(service => service.metricCpuUtilization())
        });
        const ecsMemory = new GraphWidget({
            width: 6,
            title: `ECS Memory Utilization`,
            left: props.fargateService.map(service => service.metricMemoryUtilization())
        });
        dashboard.addWidgets(ecsCPU, ecsMemory);
    }
}

Apps

Apps in the AWS CDK encapsulate Constructs and Stacks. Each component must be defined in an App. Each CDK project starts with an App! This construct is normally the root of the Construct tree.

#!/usr/bin/env node
import * as cdk from '@aws-cdk/core';
import { Construct } from '@aws-cdk/core';
import { CdkApiStack, CdkApiStackProps } from '../lib/cdk-base-stack/cdk-api-stack';
import { CdkBaseStack, CdkBaseStackProps } from '../lib/cdk-base-stack/cdk-base-stack';

class DemoApp extends Construct{
    constructor(scope: cdk.App, id:string){
        super(scope,id);
        // create a small vpc for the demo
        const vpcProps: CdkBaseStackProps = {
            stage: id, 
            description: "Base VPC for AWS Cloud Application",
            yourIpAddres: "",
            tags: {env: id},
            stackName: `PublicVpcStack-${id}` // used in cloudformation for naming stack
        }        

        const base:CdkBaseStack = new CdkBaseStack(this, `CdkVpcStack-${id}`,
            vpcProps);

        // create an API            
        const apiProps: CdkApiStackProps = {
            stage: id,
            securityGroup: base.defaultSecurityGroup,
            vpc: base.vpc,
            secretName: base.databaseCredentialsSecret.secretName
        }            

        const api: CdkApiStack = new CdkApiStack(this, `CdkApiStack-${id}`,
            apiProps);
    }
}
const app = new cdk.App();
new DemoApp(app, 'dev');

Conclusion

Let's sum up what we have just learned!

  • Use Constructs to provision resources.
  • Use higher-level constructs to provision complex cloud components.
  • Use Stacks to group resources you would like to be deployed and destroyed together.
  • An App instance is your program's entry-point. This construct is normally the root of the Construct tree.

Pro Tip: A common pattern is grouping stateful and stateless components. Stateful resources are such that cannot be easily recreated like a database. Stateless are easily re-creatable such as an AWS ECS Service.

Read my No-Nonsense Guide To AWS Cloud Development Kit to learn more!

Did you find this article valuable?

Support Phillip Ninan by becoming a sponsor. Any amount is appreciated!