Framework de infraestructura como código de AWS que permite definir recursos cloud usando lenguajes de programación como TypeScript, Python o Java, generando CloudFormation.
AWS CDK (Cloud Development Kit) es un framework de infraestructura como código que permite definir recursos AWS usando lenguajes de programación familiares como TypeScript, Python, Java, C# y Go. A diferencia de CloudFormation que usa YAML/JSON, CDK aprovecha las capacidades de los lenguajes de programación para crear abstracciones reutilizables, aplicar lógica condicional y realizar validaciones en tiempo de compilación.
El framework funciona sintetizando el código a plantillas de CloudFormation que luego se despliegan usando el motor nativo de AWS. Esta arquitectura permite mantener la robustez y características de CloudFormation (rollbacks automáticos, drift detection, stack dependencies) mientras se obtienen los beneficios de programación imperativa.
CDK introduce el concepto de «constructs» — componentes reutilizables que encapsulan uno o más recursos AWS con configuraciones predeterminadas sensatas. Estos constructs van desde mapeos directos de recursos CloudFormation hasta patrones arquitectónicos completos que configuran múltiples servicios con las mejores prácticas integradas.
CDK organiza los constructs en tres niveles de abstracción que determinan el grado de control versus conveniencia:
| Nivel | Nombre | Descripción | Ejemplo | Caso de uso |
|---|---|---|---|---|
| L1 | CFN Resources | Mapeo 1:1 con CloudFormation | CfnBucket, CfnFunction | Control granular, recursos nuevos |
| L2 | AWS Constructs | Abstracciones con defaults | s3.Bucket, lambda.Function | Desarrollo productivo |
| L3 | Patterns | Arquitecturas completas | LambdaRestApi, ApplicationLoadBalancedFargateService | Patrones comunes |
Los constructs L2 son el punto óptimo para la mayoría de casos — proporcionan defaults sensatos (cifrado habilitado, políticas IAM mínimas) mientras mantienen flexibilidad de configuración. Los constructs L3 implementan patrones arquitectónicos completos pero pueden ser demasiado rígidos para casos específicos.
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { Construct } from 'constructs';
export class UserServiceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// DynamoDB table con configuración de producción
const userTable = new dynamodb.Table(this, 'UserTable', {
tableName: 'users',
partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
encryption: dynamodb.TableEncryption.AWS_MANAGED,
pointInTimeRecovery: true,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
// Lambda function con configuración optimizada
const userHandler = new lambda.Function(this, 'UserHandler', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: {
TABLE_NAME: userTable.tableName,
},
timeout: cdk.Duration.seconds(30),
memorySize: 512,
tracing: lambda.Tracing.ACTIVE,
});
// Permisos granulares para DynamoDB
userTable.grantReadWriteData(userHandler);
// API Gateway con configuración de producción
const api = new apigateway.RestApi(this, 'UserApi', {
restApiName: 'User Service API',
description: 'API for user management',
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
},
deployOptions: {
stageName: 'prod',
throttlingRateLimit: 1000,
throttlingBurstLimit: 2000,
loggingLevel: apigateway.MethodLoggingLevel.INFO,
},
});
const users = api.root.addResource('users');
users.addMethod('GET', new apigateway.LambdaIntegration(userHandler));
users.addMethod('POST', new apigateway.LambdaIntegration(userHandler));
const userById = users.addResource('{userId}');
userById.addMethod('GET', new apigateway.LambdaIntegration(userHandler));
userById.addMethod('PUT', new apigateway.LambdaIntegration(userHandler));
userById.addMethod('DELETE', new apigateway.LambdaIntegration(userHandler));
// Outputs para integración con otros stacks
new cdk.CfnOutput(this, 'ApiUrl', {
value: api.url,
description: 'URL of the User API',
});
new cdk.CfnOutput(this, 'TableName', {
value: userTable.tableName,
description: 'Name of the DynamoDB table',
});
}
}| Aspecto | AWS CDK | Terraform |
|---|---|---|
| Lenguajes | TypeScript, Python, Java, C#, Go | HCL (HashiCorp Configuration Language) |
| Proveedores | Solo AWS (nativo) | Multi-cloud (AWS, Azure, GCP, etc.) |
| Estado | Gestionado por CloudFormation | Archivo de estado local/remoto |
| Abstracciones | Constructs L1/L2/L3 nativos | Módulos de comunidad |
| Testing | Unit tests nativos del lenguaje | Terratest, kitchen-terraform |
| IDE Support | IntelliSense completo | Extensiones HCL |
| Curva de aprendizaje | Familiar para desarrolladores | DSL específico |
| Rollbacks | Automáticos via CloudFormation | Manuales o via CI/CD |
| Drift detection | Nativo en CloudFormation | Terraform plan |
| Ecosistema | Constructs Hub, CDK Patterns | Terraform Registry |
Para equipos considerando migrar de Terraform a CDK:
cdk import para importar recursos existentesCDK permite testing unitario real usando frameworks del lenguaje:
import { Template, Match } from 'aws-cdk-lib/assertions';
import { UserServiceStack } from '../lib/user-service-stack';
import { App } from 'aws-cdk-lib';
test('DynamoDB table created with correct configuration', () => {
const app = new App();
const stack = new UserServiceStack(app, 'TestStack');
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::DynamoDB::Table', {
BillingMode: 'PAY_PER_REQUEST',
SSESpecification: {
SSEEnabled: true,
},
PointInTimeRecoverySpecification: {
PointInTimeRecoveryEnabled: true,
},
});
});
test('Lambda has correct environment variables', () => {
const app = new App();
const stack = new UserServiceStack(app, 'TestStack');
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::Lambda::Function', {
Environment: {
Variables: {
TABLE_NAME: { Ref: Match.stringLikeRegexp('UserTable') },
},
},
});
});// Stack de base de datos
export class DatabaseStack extends cdk.Stack {
public readonly userTable: dynamodb.Table;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.userTable = new dynamodb.Table(this, 'UserTable', {
// configuración...
});
}
}
// Stack de API que consume la base de datos
export class ApiStack extends cdk.Stack {
constructor(scope: Construct, id: string,
databaseStack: DatabaseStack,
props?: cdk.StackProps) {
super(scope, id, props);
const handler = new lambda.Function(this, 'Handler', {
environment: {
TABLE_NAME: databaseStack.userTable.tableName,
},
// configuración...
});
databaseStack.userTable.grantReadWriteData(handler);
}
}export interface ApiLambdaProps {
tableName: string;
timeout?: cdk.Duration;
memorySize?: number;
}
export class ApiLambda extends Construct {
public readonly function: lambda.Function;
public readonly api: apigateway.RestApi;
constructor(scope: Construct, id: string, props: ApiLambdaProps) {
super(scope, id);
this.function = new lambda.Function(this, 'Function', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: { TABLE_NAME: props.tableName },
timeout: props.timeout ?? cdk.Duration.seconds(30),
memorySize: props.memorySize ?? 512,
});
this.api = new apigateway.LambdaRestApi(this, 'Api', {
handler: this.function,
proxy: false,
});
}
}CDK representa un cambio paradigmático en infraestructura como código al eliminar la barrera artificial entre código de aplicación e infraestructura. Para equipos de ingeniería senior, esto significa poder aplicar las mismas prácticas de desarrollo — testing unitario, refactoring, abstracciones, composición — a la definición de infraestructura.
La capacidad de crear constructs reutilizables permite establecer «golden paths» organizacionales que encapsulan mejores prácticas de seguridad, observabilidad y costo. Un construct L3 personalizado puede garantizar que todas las APIs incluyan logging estructurado, métricas CloudWatch, y políticas IAM mínimas sin requerir conocimiento experto de cada desarrollador.
El modelo de testing nativo elimina la necesidad de herramientas externas como Terratest, permitiendo TDD real para infraestructura. Esto es especialmente valioso en organizaciones que priorizan calidad de código y cobertura de testing. La integración con IDEs proporciona autocompletado, detección de errores en tiempo real, y refactoring automático — capacidades imposibles con DSLs como HCL o YAML.
Práctica de definir y gestionar infraestructura mediante archivos de configuración versionados en lugar de procesos manuales. Fundamento de la automatización moderna de operaciones.
Superset tipado de JavaScript que añade tipos estáticos opcionales, mejorando la productividad del desarrollador, la detección de errores y la mantenibilidad del código.
Servicio nativo de AWS para definir y aprovisionar infraestructura como código usando plantillas YAML o JSON, con gestión de estado y rollback automático.