Cloud AWS
Arquitectura Serverless con AWS CDK: Guía completa
Aprende a diseñar e implementar una arquitectura serverless completa usando AWS CDK, Lambda, API Gateway y DynamoDB.
AWS CDK (Cloud Development Kit) te permite definir tu infraestructura cloud usando lenguajes de programación familiares. En este artículo, construiremos una arquitectura serverless completa.
¿Por qué CDK sobre CloudFormation?
CDK ofrece ventajas significativas:
- Type safety: Autocompletado y detección de errores en tiempo de desarrollo
- Reutilización: Crea componentes reutilizables
- Lenguajes familiares: TypeScript, Python, Java, etc.
- Menor código: Menos líneas que CloudFormation equivalente
Arquitectura que construiremos
Crearemos una API REST con:
- API Gateway para endpoints HTTP
- Lambda functions para lógica de negocio
- DynamoDB para persistencia
- S3 para almacenamiento de archivos
- CloudWatch para logs y monitoreo
Setup inicial
npm install -g aws-cdk
mkdir my-serverless-app && cd my-serverless-app
cdk init app --language=typescript
npm install @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-dynamodb
Definiendo la infraestructura
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 ServerlessStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Tabla DynamoDB
const table = new dynamodb.Table(this, "ItemsTable", {
partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY, // Solo para dev
});
// Lambda function
const handler = new lambda.Function(this, "ItemsHandler", {
runtime: lambda.Runtime.NODEJS_18_X,
code: lambda.Code.fromAsset("lambda"),
handler: "index.handler",
environment: {
TABLE_NAME: table.tableName,
},
});
// Dar permisos a Lambda para acceder a DynamoDB
table.grantReadWriteData(handler);
// API Gateway
const api = new apigateway.RestApi(this, "ItemsApi", {
restApiName: "Items Service",
description: "API for managing items",
});
const items = api.root.addResource("items");
items.addMethod("GET", new apigateway.LambdaIntegration(handler));
items.addMethod("POST", new apigateway.LambdaIntegration(handler));
const item = items.addResource("{id}");
item.addMethod("GET", new apigateway.LambdaIntegration(handler));
item.addMethod("PUT", new apigateway.LambdaIntegration(handler));
item.addMethod("DELETE", new apigateway.LambdaIntegration(handler));
// Outputs
new cdk.CfnOutput(this, "ApiUrl", {
value: api.url,
description: "URL del API Gateway",
});
}
}
Código Lambda
// lambda/index.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand, PutCommand, DeleteCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const ddb = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME!;
export const handler = async (event: any) => {
const method = event.httpMethod;
const path = event.path;
try {
switch (\`\${method} \${path}\`) {
case 'GET /items':
return await listItems();
case 'GET /items/{id}':
return await getItem(event.pathParameters.id);
case 'POST /items':
return await createItem(JSON.parse(event.body));
case 'PUT /items/{id}':
return await updateItem(event.pathParameters.id, JSON.parse(event.body));
case 'DELETE /items/{id}':
return await deleteItem(event.pathParameters.id);
default:
return { statusCode: 404, body: 'Not Found' };
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};
async function listItems() {
const result = await ddb.send(new ScanCommand({ TableName: TABLE_NAME }));
return {
statusCode: 200,
body: JSON.stringify(result.Items),
};
}
async function getItem(id: string) {
const result = await ddb.send(
new GetCommand({
TableName: TABLE_NAME,
Key: { id },
})
);
return {
statusCode: 200,
body: JSON.stringify(result.Item),
};
}
async function createItem(item: any) {
await ddb.send(
new PutCommand({
TableName: TABLE_NAME,
Item: { ...item, id: crypto.randomUUID() },
})
);
return {
statusCode: 201,
body: JSON.stringify({ message: 'Item created' }),
};
}
Deploy
# Compilar TypeScript
npm run build
# Sintetizar CloudFormation
cdk synth
# Deploy
cdk deploy
Mejores prácticas
- Separación de stacks: Usa múltiples stacks para diferentes ambientes
- Variables de entorno: Usa context values para configuración
- Testing: Escribe tests unitarios con aws-cdk assertions
- Cost optimization: Usa Reserved Concurrency para controlar costos
Monitoreo y logs
CDK automáticamente crea CloudWatch Logs. Puedes agregar alarmas:
```typescript import * as cloudwatch from ‘aws-cdk-lib/aws-cloudwatch’;
const errorAlarm = new cloudwatch.Alarm(this, ‘ErrorAlarm’, {
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
const errorAlarm = new cloudwatch.Alarm(this, 'ErrorAlarm', {
metric: handler.metricErrors(),
threshold: 5,
evaluationPeriods: 1,
});
``DK simplifica enormemente la creación de arquitecturas serverless. Con type safety y componentes reutilizables, puedes construir infraestructura robusta de manera eficiente.
¿Usas CDK en producción? Comparte tu experiencia. ¿Te gustó este artículo?
Suscríbete para recibir más contenido sobre desarrollo web y programación
Contáctame