Introduction to AWS CDK
AWS Cloud Development Kit (CDK) is an open-source software development framework that allows you to define cloud infrastructure using familiar programming languages like Java, and provision it through AWS CloudFormation.
CDK Setup and Installation
Prerequisites
<!-- Maven Dependencies -->
<properties>
<cdk.version>2.95.0</cdk.version>
</properties>
<dependencies>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>aws-cdk-lib</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.constructs</groupId>
<artifactId>constructs</artifactId>
<version>10.2.70</version>
</dependency>
</dependencies>
CDK Project Structure
my-cdk-app/ ├── pom.xml ├── cdk.json ├── src/ │ └── main/ │ └── java/ │ └── com/ │ └── mycompany/ │ ├── MainApp.java │ ├── stacks/ │ │ ├── VpcStack.java │ │ ├── DatabaseStack.java │ │ └── ApiStack.java │ └── constructs/ │ └── CustomConstruct.java
Basic CDK App
package com.mycompany;
import software.amazon.awscdk.App;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.s3.Bucket;
import software.constructs.Construct;
public class MainApp {
public static void main(String[] args) {
App app = new App();
new MyFirstStack(app, "MyFirstStack", StackProps.builder()
.build());
app.synth();
}
}
class MyFirstStack extends Stack {
public MyFirstStack(Construct scope, String id, StackProps props) {
super(scope, id, props);
// Create S3 bucket
Bucket.Builder.create(this, "MyFirstBucket")
.versioned(true)
.build();
}
}
Core CDK Concepts
Stacks
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.Vpc;
import software.constructs.Construct;
public class NetworkStack extends Stack {
private final Vpc vpc;
public NetworkStack(Construct scope, String id, StackProps props) {
super(scope, id, props);
this.vpc = Vpc.Builder.create(this, "MainVpc")
.maxAzs(3)
.natGateways(1)
.build();
}
public Vpc getVpc() {
return vpc;
}
}
Constructs
package com.mycompany.constructs;
import software.amazon.awscdk.services.ec2.*;
import software.amazon.awscdk.services.iam.*;
import software.constructs.Construct;
import java.util.List;
public class WebServerConstruct extends Construct {
private final Instance instance;
public WebServerConstruct(Construct scope, String id, WebServerProps props) {
super(scope, id);
// Security Group
SecurityGroup sg = SecurityGroup.Builder.create(this, "WebServerSG")
.vpc(props.getVpc())
.description("Security group for web server")
.allowAllOutbound(true)
.build();
sg.addIngressRule(
Peer.anyIpv4(),
Port.tcp(80),
"Allow HTTP traffic"
);
sg.addIngressRule(
Peer.anyIpv4(),
Port.tcp(443),
"Allow HTTPS traffic"
);
// IAM Role
Role role = Role.Builder.create(this, "WebServerRole")
.assumedBy(new ServicePrincipal("ec2.amazonaws.com"))
.build();
role.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")
);
// EC2 Instance
this.instance = Instance.Builder.create(this, "WebServer")
.instanceType(InstanceType.of(InstanceClass.T3, InstanceSize.MICRO)
.machineImage(MachineImage.latestAmazonLinux2023())
.vpc(props.getVpc())
.securityGroup(sg)
.role(role)
.build();
}
public Instance getInstance() {
return instance;
}
public static class WebServerProps {
private final IVpc vpc;
public WebServerProps(IVpc vpc) {
this.vpc = vpc;
}
public IVpc getVpc() {
return vpc;
}
}
}
Infrastructure as Code Examples
VPC Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.*;
import software.constructs.Construct;
public class VpcStack extends Stack {
private final Vpc vpc;
private final SecurityGroup appSecurityGroup;
private final SecurityGroup dbSecurityGroup;
public VpcStack(Construct scope, String id, StackProps props) {
super(scope, id, props);
// Create VPC with public and private subnets
this.vpc = Vpc.Builder.create(this, "ApplicationVpc")
.vpcName("application-vpc")
.maxAzs(3)
.natGateways(1)
.subnetConfiguration(List.of(
SubnetConfiguration.builder()
.name("Public")
.subnetType(SubnetType.PUBLIC)
.cidrMask(24)
.build(),
SubnetConfiguration.builder()
.name("Private")
.subnetType(SubnetType.PRIVATE_WITH_EGRESS)
.cidrMask(24)
.build(),
SubnetConfiguration.builder()
.name("Isolated")
.subnetType(SubnetType.PRIVATE_ISOLATED)
.cidrMask(24)
.build()
))
.build();
// Application Security Group
this.appSecurityGroup = SecurityGroup.Builder.create(this, "AppSecurityGroup")
.vpc(vpc)
.description("Security group for application instances")
.allowAllOutbound(true)
.build();
appSecurityGroup.addIngressRule(
Peer.anyIpv4(),
Port.tcp(80),
"Allow HTTP traffic"
);
appSecurityGroup.addIngressRule(
Peer.anyIpv4(),
Port.tcp(443),
"Allow HTTPS traffic"
);
// Database Security Group
this.dbSecurityGroup = SecurityGroup.Builder.create(this, "DbSecurityGroup")
.vpc(vpc)
.description("Security group for database")
.allowAllOutbound(true)
.build();
dbSecurityGroup.addIngressRule(
appSecurityGroup,
Port.tcp(3306),
"Allow MySQL from app servers"
);
}
public Vpc getVpc() {
return vpc;
}
public SecurityGroup getAppSecurityGroup() {
return appSecurityGroup;
}
public SecurityGroup getDbSecurityGroup() {
return dbSecurityGroup;
}
}
Database Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.*;
import software.amazon.awscdk.services.rds.*;
import software.constructs.Construct;
import java.util.List;
public class DatabaseStack extends Stack {
private final DatabaseInstance database;
public DatabaseStack(Construct scope, String id, DatabaseStackProps props) {
super(scope, id, props);
// Database Subnet Group
SubnetGroup subnetGroup = SubnetGroup.Builder.create(this, "DBSubnetGroup")
.description("Subnet group for database")
.vpc(props.getVpc())
.vpcSubnets(SubnetSelection.builder()
.subnetType(SubnetType.PRIVATE_ISOLATED)
.build())
.build();
// Database Credentials
DatabaseSecret secret = DatabaseSecret.Builder.create(this, "DBCredentials")
.username("admin")
.build();
// RDS Instance
this.database = DatabaseInstance.Builder.create(this, "ApplicationDB")
.engine(DatabaseInstanceEngine.mysql(
MySqlInstanceEngineProps.builder()
.version(MysqlEngineVersion.VER_8_0)
.build()))
.instanceType(InstanceType.of(InstanceClass.T3, InstanceSize.MICRO))
.vpc(props.getVpc())
.vpcSubnets(SubnetSelection.builder()
.subnetType(SubnetType.PRIVATE_ISOLATED)
.build())
.securityGroups(List.of(props.getDbSecurityGroup()))
.subnetGroup(subnetGroup)
.credentials(Credentials.fromSecret(secret))
.databaseName("applicationdb")
.backupRetention(software.amazon.awscdk.Duration.days(7))
.deletionProtection(false)
.build();
}
public DatabaseInstance getDatabase() {
return database;
}
public static class DatabaseStackProps implements StackProps {
private final IVpc vpc;
private final ISecurityGroup dbSecurityGroup;
public DatabaseStackProps(IVpc vpc, ISecurityGroup dbSecurityGroup) {
this.vpc = vpc;
this.dbSecurityGroup = dbSecurityGroup;
}
public IVpc getVpc() {
return vpc;
}
public ISecurityGroup getDbSecurityGroup() {
return dbSecurityGroup;
}
}
}
Application Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.*;
import software.amazon.awscdk.services.elasticloadbalancingv2.*;
import software.amazon.awscdk.services.autoscaling.*;
import software.constructs.Construct;
import java.util.List;
import java.util.Map;
public class ApplicationStack extends Stack {
private final ApplicationLoadBalancer alb;
private final AutoScalingGroup asg;
public ApplicationStack(Construct scope, String id, ApplicationStackProps props) {
super(scope, id, props);
// Application Load Balancer
this.alb = ApplicationLoadBalancer.Builder.create(this, "ApplicationLB")
.vpc(props.getVpc())
.internetFacing(true)
.loadBalancerName("app-load-balancer")
.build();
// ALB Listener
ApplicationListener listener = alb.addListener("HttpListener",
BaseApplicationListenerProps.builder()
.port(80)
.open(true)
.build());
// Auto Scaling Group
this.asg = AutoScalingGroup.Builder.create(this, "ApplicationASG")
.vpc(props.getVpc())
.instanceType(InstanceType.of(InstanceClass.T3, InstanceSize.MICRO))
.machineImage(MachineImage.latestAmazonLinux2023())
.securityGroup(props.getAppSecurityGroup())
.minCapacity(2)
.maxCapacity(10)
.desiredCapacity(2)
.userData(UserData.forLinux())
.build();
// Add user data to instances
asg.getUserData().addCommands(
"yum update -y",
"yum install -y httpd",
"systemctl start httpd",
"systemctl enable httpd",
"echo '<h1>Hello from CDK!</h1>' > /var/www/html/index.html"
);
// Add target group to ALB
listener.addTargets("ApplicationTargets",
AddApplicationTargetsProps.builder()
.port(80)
.targets(List.of(asg))
.healthCheck(HealthCheck.builder()
.path("/")
.port("80")
.build())
.build());
// Auto Scaling Policies
asg.scaleOnCpuUtilization("CpuScaling",
CpuUtilizationScalingProps.builder()
.targetUtilizationPercent(70)
.build());
asg.scaleOnRequestCount("RequestScaling",
RequestCountScalingProps.builder()
.targetRequestsPerMinute(1000)
.build());
}
public ApplicationLoadBalancer getAlb() {
return alb;
}
public AutoScalingGroup getAsg() {
return asg;
}
public static class ApplicationStackProps implements StackProps {
private final IVpc vpc;
private final ISecurityGroup appSecurityGroup;
public ApplicationStackProps(IVpc vpc, ISecurityGroup appSecurityGroup) {
this.vpc = vpc;
this.appSecurityGroup = appSecurityGroup;
}
public IVpc getVpc() {
return vpc;
}
public ISecurityGroup getAppSecurityGroup() {
return appSecurityGroup;
}
}
}
Lambda Functions with CDK
Lambda Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.lambda.*;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.apigateway.*;
import software.amazon.awscdk.services.iam.*;
import software.amazon.awscdk.services.dynamodb.*;
import software.constructs.Construct;
import java.util.List;
import java.util.Map;
public class LambdaApiStack extends Stack {
private final RestApi api;
private final Table dynamoTable;
public LambdaApiStack(Construct scope, String id, StackProps props) {
super(scope, id, props);
// DynamoDB Table
this.dynamoTable = Table.Builder.create(this, "UsersTable")
.partitionKey(Attribute.builder()
.name("userId")
.type(AttributeType.STRING)
.build())
.billingMode(BillingMode.PAY_PER_REQUEST)
.removalPolicy(software.amazon.awscdk.RemovalPolicy.DESTROY)
.build();
// IAM Role for Lambda
Role lambdaRole = Role.Builder.create(this, "LambdaExecutionRole")
.assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
.build();
lambdaRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
);
dynamoTable.grantReadWriteData(lambdaRole);
// Lambda Functions
Function createUserFunction = Function.Builder.create(this, "CreateUserFunction")
.runtime(Runtime.JAVA_11)
.handler("com.mycompany.handlers.UserHandler::createUser")
.code(Code.fromAsset("../lambda-java/target/user-handler.jar"))
.role(lambdaRole)
.timeout(software.amazon.awscdk.Duration.seconds(30))
.memorySize(512)
.environment(Map.of(
"USERS_TABLE", dynamoTable.getTableName()
))
.build();
Function getUserFunction = Function.Builder.create(this, "GetUserFunction")
.runtime(Runtime.JAVA_11)
.handler("com.mycompany.handlers.UserHandler::getUser")
.code(Code.fromAsset("../lambda-java/target/user-handler.jar"))
.role(lambdaRole)
.timeout(software.amazon.awscdk.Duration.seconds(30))
.memorySize(512)
.environment(Map.of(
"USERS_TABLE", dynamoTable.getTableName()
))
.build();
// API Gateway
this.api = RestApi.Builder.create(this, "UserApi")
.restApiName("User Service")
.description("API for user management")
.defaultCorsPreflightOptions(CorsOptions.builder()
.allowOrigins(Cors.ALL_ORIGINS)
.allowMethods(Cors.ALL_METHODS)
.allowHeaders(List.of("*"))
.build())
.build();
// API Resources and Methods
LambdaIntegration createUserIntegration = new LambdaIntegration(createUserFunction);
LambdaIntegration getUserIntegration = new LambdaIntegration(getUserFunction);
Resource usersResource = api.getRoot().addResource("users");
usersResource.addMethod("POST", createUserIntegration);
Resource userResource = usersResource.addResource("{userId}");
userResource.addMethod("GET", getUserIntegration);
}
public RestApi getApi() {
return api;
}
public Table getDynamoTable() {
return dynamoTable;
}
}
Lambda Handler Code
// Lambda handler implementation
package com.mycompany.handlers;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class UserHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
private final DynamoDbClient dynamoDbClient;
private final ObjectMapper objectMapper;
private final String tableName;
public UserHandler() {
this.dynamoDbClient = DynamoDbClient.create();
this.objectMapper = new ObjectMapper();
this.tableName = System.getenv("USERS_TABLE");
}
@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
try {
String httpMethod = input.getHttpMethod();
switch (httpMethod) {
case "POST":
return createUser(input);
case "GET":
return getUser(input);
default:
return createResponse(405, "Method Not Allowed");
}
} catch (Exception e) {
context.getLogger().log("Error: " + e.getMessage());
return createResponse(500, "Internal Server Error");
}
}
private APIGatewayProxyResponseEvent createUser(APIGatewayProxyRequestEvent input) throws Exception {
Map<String, Object> requestBody = objectMapper.readValue(input.getBody(), Map.class);
String userId = UUID.randomUUID().toString();
String name = (String) requestBody.get("name");
String email = (String) requestBody.get("email");
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(userId).build());
item.put("name", AttributeValue.builder().s(name).build());
item.put("email", AttributeValue.builder().s(email).build());
item.put("createdAt", AttributeValue.builder().s(java.time.Instant.now().toString()).build());
PutItemRequest putRequest = PutItemRequest.builder()
.tableName(tableName)
.item(item)
.build();
dynamoDbClient.putItem(putRequest);
Map<String, String> response = new HashMap<>();
response.put("userId", userId);
response.put("message", "User created successfully");
return createResponse(201, objectMapper.writeValueAsString(response));
}
private APIGatewayProxyResponseEvent getUser(APIGatewayProxyRequestEvent input) {
String userId = input.getPathParameters().get("userId");
GetItemRequest getRequest = GetItemRequest.builder()
.tableName(tableName)
.key(Map.of("userId", AttributeValue.builder().s(userId).build()))
.build();
GetItemResponse response = dynamoDbClient.getItem(getRequest);
if (response.item().isEmpty()) {
return createResponse(404, "User not found");
}
Map<String, String> user = new HashMap<>();
user.put("userId", response.item().get("userId").s());
user.put("name", response.item().get("name").s());
user.put("email", response.item().get("email").s());
try {
return createResponse(200, objectMapper.writeValueAsString(user));
} catch (Exception e) {
return createResponse(500, "Error serializing response");
}
}
private APIGatewayProxyResponseEvent createResponse(int statusCode, String body) {
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
response.setStatusCode(statusCode);
response.setBody(body);
response.setHeaders(Map.of(
"Content-Type", "application/json",
"Access-Control-Allow-Origin", "*"
));
return response;
}
}
Containerized Applications with ECS
ECS Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.*;
import software.amazon.awscdk.services.ecs.*;
import software.amazon.awscdk.services.ecs.patterns.*;
import software.amazon.awscdk.services.elasticloadbalancingv2.*;
import software.constructs.Construct;
import java.util.Map;
public class EcsStack extends Stack {
private final ApplicationLoadBalancedFargateService fargateService;
public EcsStack(Construct scope, String id, EcsStackProps props) {
super(scope, id, props);
// ECS Cluster
Cluster cluster = Cluster.Builder.create(this, "AppCluster")
.vpc(props.getVpc())
.clusterName("application-cluster")
.build();
// Fargate Service with Load Balancer
this.fargateService = ApplicationLoadBalancedFargateService.Builder.create(this, "AppService")
.cluster(cluster)
.cpu(256)
.memoryLimitMiB(512)
.desiredCount(2)
.taskImageOptions(ApplicationLoadBalancedTaskImageOptions.builder()
.image(ContainerImage.fromRegistry("nginx:latest"))
.containerPort(80)
.environment(Map.of(
"ENVIRONMENT", "production",
"DATABASE_URL", props.getDatabaseUrl()
))
.build())
.publicLoadBalancer(true)
.build();
// Configure Auto Scaling
ScalableTaskCount scaling = fargateService.getService().autoScaleTaskCount(
AutoScaleTaskCountProps.builder()
.minCapacity(2)
.maxCapacity(10)
.build());
scaling.scaleOnCpuUtilization("CpuScaling",
CpuUtilizationScalingProps.builder()
.targetUtilizationPercent(70)
.scaleInCooldown(software.amazon.awscdk.Duration.seconds(60))
.scaleOutCooldown(software.amazon.awscdk.Duration.seconds(60))
.build());
// Configure Health Check
fargateService.getTargetGroup().configureHealthCheck(
HealthCheck.builder()
.path("/")
.port("80")
.healthyHttpCodes("200")
.healthyThresholdCount(2)
.unhealthyThresholdCount(3)
.timeout(software.amazon.awscdk.Duration.seconds(5))
.interval(software.amazon.awscdk.Duration.seconds(30))
.build());
}
public ApplicationLoadBalancedFargateService getFargateService() {
return fargateService;
}
public static class EcsStackProps implements StackProps {
private final IVpc vpc;
private final String databaseUrl;
public EcsStackProps(IVpc vpc, String databaseUrl) {
this.vpc = vpc;
this.databaseUrl = databaseUrl;
}
public IVpc getVpc() {
return vpc;
}
public String getDatabaseUrl() {
return databaseUrl;
}
}
}
Monitoring and Logging
Monitoring Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.cloudwatch.*;
import software.amazon.awscdk.services.sns.*;
import software.amazon.awscdk.services.cloudwatch.actions.*;
import software.constructs.Construct;
import java.util.List;
public class MonitoringStack extends Stack {
private final Topic alarmTopic;
public MonitoringStack(Construct scope, String id, MonitoringStackProps props) {
super(scope, id, props);
// SNS Topic for alarms
this.alarmTopic = Topic.Builder.create(this, "AlarmTopic")
.topicName("application-alarms")
.build();
// CPU Utilization Alarm
Alarm cpuAlarm = Alarm.Builder.create(this, "HighCPUAlarm")
.alarmName("HighCPUUtilization")
.alarmDescription("CPU utilization is too high")
.metric(props.getCpuMetric())
.threshold(80)
.evaluationPeriods(3)
.datapointsToAlarm(2)
.comparisonOperator(ComparisonOperator.GREATER_THAN_THRESHOLD)
.build();
cpuAlarm.addAlarmAction(new SnsAction(alarmTopic));
// Memory Utilization Alarm
Alarm memoryAlarm = Alarm.Builder.create(this, "HighMemoryAlarm")
.alarmName("HighMemoryUtilization")
.alarmDescription("Memory utilization is too high")
.metric(props.getMemoryMetric())
.threshold(85)
.evaluationPeriods(3)
.datapointsToAlarm(2)
.comparisonOperator(ComparisonOperator.GREATER_THAN_THRESHOLD)
.build();
memoryAlarm.addAlarmAction(new SnsAction(alarmTopic));
// Custom Dashboard
Dashboard dashboard = Dashboard.Builder.create(this, "ApplicationDashboard")
.dashboardName("Application-Monitoring")
.build();
dashboard.addWidgets(
GraphWidget.Builder.create()
.title("CPU Utilization")
.left(List.of(props.getCpuMetric()))
.width(12)
.build(),
GraphWidget.Builder.create()
.title("Memory Utilization")
.left(List.of(props.getMemoryMetric()))
.width(12)
.build(),
AlarmWidget.Builder.create()
.title("Alarms")
.alarms(List.of(cpuAlarm, memoryAlarm))
.width(12)
.build()
);
}
public Topic getAlarmTopic() {
return alarmTopic;
}
public static class MonitoringStackProps implements StackProps {
private final IMetric cpuMetric;
private final IMetric memoryMetric;
public MonitoringStackProps(IMetric cpuMetric, IMetric memoryMetric) {
this.cpuMetric = cpuMetric;
this.memoryMetric = memoryMetric;
}
public IMetric getCpuMetric() {
return cpuMetric;
}
public IMetric getMemoryMetric() {
return memoryMetric;
}
}
}
Advanced CDK Patterns
Custom Resource
package com.mycompany.constructs;
import software.amazon.awscdk.CustomResource;
import software.amazon.awscdk.CustomResourceProps;
import software.amazon.awscdk.services.lambda.*;
import software.constructs.Construct;
import java.util.Map;
public class CustomConfigConstruct extends Construct {
public CustomConfigConstruct(Construct scope, String id, CustomConfigProps props) {
super(scope, id);
// Lambda function for custom resource
Function customResourceHandler = Function.Builder.create(this, "CustomResourceHandler")
.runtime(Runtime.PYTHON_3_9)
.handler("index.handler")
.code(Code.fromInline(
"import json\n" +
"import boto3\n" +
"def handler(event, context):\n" +
" print(event)\n" +
" return {'PhysicalResourceId': event['LogicalResourceId']}"
))
.timeout(software.amazon.awscdk.Duration.seconds(300))
.build();
// Custom Resource
CustomResource.Builder.create(this, "CustomConfig")
.serviceToken(customResourceHandler.getFunctionArn())
.properties(Map.of(
"ConfigParameter", props.getConfigValue(),
"Environment", props.getEnvironment()
))
.build();
}
public static class CustomConfigProps {
private final String configValue;
private final String environment;
public CustomConfigProps(String configValue, String environment) {
this.configValue = configValue;
this.environment = environment;
}
public String getConfigValue() {
return configValue;
}
public String getEnvironment() {
return environment;
}
}
}
Multi-Environment Deployment
package com.mycompany;
import software.amazon.awscdk.App;
import software.amazon.awscdk.Environment;
import software.amazon.awscdk.StackProps;
import com.mycompany.stacks.*;
public class MultiEnvironmentApp {
public static void main(String[] args) {
App app = new App();
// Development Environment
Environment devEnv = Environment.builder()
.account("111111111111")
.region("us-east-1")
.build();
// Production Environment
Environment prodEnv = Environment.builder()
.account("222222222222")
.region("us-east-1")
.build();
// Development Stacks
VpcStack devVpcStack = new VpcStack(app, "DevVpcStack",
StackProps.builder()
.env(devEnv)
.build());
DatabaseStack devDbStack = new DatabaseStack(app, "DevDatabaseStack",
new DatabaseStack.DatabaseStackProps(
devVpcStack.getVpc(),
devVpcStack.getDbSecurityGroup()
));
ApplicationStack devAppStack = new ApplicationStack(app, "DevApplicationStack",
new ApplicationStack.ApplicationStackProps(
devVpcStack.getVpc(),
devVpcStack.getAppSecurityGroup()
));
// Production Stacks
VpcStack prodVpcStack = new VpcStack(app, "ProdVpcStack",
StackProps.builder()
.env(prodEnv)
.build());
DatabaseStack prodDbStack = new DatabaseStack(app, "ProdDatabaseStack",
new DatabaseStack.DatabaseStackProps(
prodVpcStack.getVpc(),
prodVpcStack.getDbSecurityGroup()
));
ApplicationStack prodAppStack = new ApplicationStack(app, "ProdApplicationStack",
new ApplicationStack.ApplicationStackProps(
prodVpcStack.getVpc(),
prodVpcStack.getAppSecurityGroup()
));
app.synth();
}
}
Testing CDK Constructs
CDK Testing
package com.mycompany.test;
import software.amazon.awscdk.assertions.Template;
import software.amazon.awscdk.App;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.services.s3.Bucket;
import org.junit.jupiter.api.Test;
import java.util.Map;
public class CdkTest {
@Test
public void testBucketCreated() {
// Given
App app = new App();
Stack stack = new Stack(app, "TestStack");
// When
Bucket.Builder.create(stack, "TestBucket")
.versioned(true)
.build();
// Then
Template template = Template.fromStack(stack);
template.hasResourceProperties("AWS::S3::Bucket",
Map.of("VersioningConfiguration",
Map.of("Status", "Enabled")));
template.resourceCountIs("AWS::S3::Bucket", 1);
}
@Test
public void testVpcConfiguration() {
// Given
App app = new App();
Stack stack = new Stack(app, "TestStack");
// When
new com.mycompany.stacks.VpcStack(stack, "TestVpcStack", null);
// Then
Template template = Template.fromStack(stack);
template.hasResourceProperties("AWS::EC2::VPC",
Map.of("EnableDnsHostnames", true));
template.hasResourceProperties("AWS::EC2::Subnet",
Map.of("MapPublicIpOnLaunch", true));
}
}
CDK Best Practices
Configuration Management
package com.mycompany.config;
import software.amazon.awscdk.StackProps;
import java.util.Map;
public class EnvironmentConfig {
public static StackProps getDevStackProps() {
return StackProps.builder()
.tags(Map.of(
"Environment", "development",
"Team", "platform",
"CostCenter", "12345"
))
.build();
}
public static StackProps getProdStackProps() {
return StackProps.builder()
.tags(Map.of(
"Environment", "production",
"Team", "platform",
"CostCenter", "12345",
"Critical", "true"
))
.build();
}
public static Map<String, Object> getAppConfig(String environment) {
switch (environment) {
case "dev":
return Map.of(
"instanceType", "t3.micro",
"minCapacity", 1,
"maxCapacity", 3,
"databaseSize", "db.t3.small"
);
case "prod":
return Map.of(
"instanceType", "t3.large",
"minCapacity", 2,
"maxCapacity", 10,
"databaseSize", "db.r5.large"
);
default:
throw new IllegalArgumentException("Unknown environment: " + environment);
}
}
}
Security Best Practices
package com.mycompany.constructs;
import software.amazon.awscdk.services.iam.*;
import software.constructs.Construct;
import java.util.List;
public class SecureIamConstruct extends Construct {
public SecureIamConstruct(Construct scope, String id, SecureIamProps props) {
super(scope, id);
// Principle of least privilege
PolicyDocument policyDoc = PolicyDocument.Builder.create()
.statements(List.of(
PolicyStatement.Builder.create()
.effect(Effect.ALLOW)
.actions(List.of(
"s3:GetObject",
"s3:ListBucket"
))
.resources(List.of(
props.getS3BucketArn(),
props.getS3BucketArn() + "/*"
))
.build()
))
.build();
Role secureRole = Role.Builder.create(this, "SecureRole")
.assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
.inlinePolicies(Map.of("S3ReadPolicy", policyDoc))
.build();
// Add managed policies carefully
secureRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
);
}
public static class SecureIamProps {
private final String s3BucketArn;
public SecureIamProps(String s3BucketArn) {
this.s3BucketArn = s3BucketArn;
}
public String getS3BucketArn() {
return s3BucketArn;
}
}
}
CDK Pipelines
Pipeline Stack
package com.mycompany.stacks;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.pipelines.*;
import software.amazon.awscdk.services.codecommit.*;
import software.constructs.Construct;
public class PipelineStack extends Stack {
public PipelineStack(Construct scope, String id, StackProps props) {
super(scope, id, props);
// CodeCommit Repository
Repository codeRepo = Repository.Builder.create(this, "AppRepository")
.repositoryName("my-cdk-application")
.build();
// CDK Pipeline
CodePipeline pipeline = CodePipeline.Builder.create(this, "Pipeline")
.pipelineName("AppPipeline")
.synth(ShellStep.Builder.create("Synth")
.input(CodePipelineSource.codeCommit(codeRepo, "main"))
.commands(List.of(
"npm install -g aws-cdk",
"mvn compile",
"cdk synth"
))
.build())
.build();
// Add stages
pipeline.addStage(new ApplicationStage(this, "Dev",
StageProps.builder()
.env(software.amazon.awscdk.Environment.builder()
.account("111111111111")
.region("us-east-1")
.build())
.build()));
pipeline.addStage(new ApplicationStage(this, "Prod",
StageProps.builder()
.env(software.amazon.awscdk.Environment.builder()
.account("222222222222")
.region("us-east-1")
.build())
.build()));
}
}
class ApplicationStage extends Stage {
public ApplicationStage(Construct scope, String id, StageProps props) {
super(scope, id, props);
new VpcStack(this, "VpcStack", null);
new DatabaseStack(this, "DatabaseStack", null);
new ApplicationStack(this, "ApplicationStack", null);
}
}
Conclusion
AWS CDK for Java provides a powerful way to define cloud infrastructure using familiar Java programming constructs. Key benefits include:
- Type Safety: Compile-time checking of infrastructure code
- Reusability: Create reusable constructs and patterns
- Testability: Write unit tests for your infrastructure
- IDE Support: Full IDE support with autocomplete and refactoring
- Modularity: Organize infrastructure into logical stacks and constructs
By leveraging CDK with Java, you can build robust, maintainable, and scalable cloud infrastructure while using the same development tools and practices you use for application code.