Migration von Serverless Framework zu AWS CDK: Teil 2 - CDK-Umgebung Einrichten
Lerne, wie du ein CDK-Projekt für serverless Anwendungen strukturierst, TypeScript für Lambda-Entwicklung konfigurierst und Muster etablierst, die die Migration vom Serverless Framework erleichtern.
Woche 1 unserer CDK-Migration. Die Entscheidung war getroffen, das Budget genehmigt, und mein 12-köpfiges Ingenieursteam schaute mich erwartungsvoll an. "Also, wo fangen wir an?"
Ich hatte CDK in persönlichen Projekten verwendet, aber es zu skalieren, um 47 Lambda-Funktionen über 4 Umgebungen mit 12 Entwicklern zu verwalten? Das war anders. Wir brauchten Struktur, Konventionen und Muster, die nicht nur für mich, sondern für das gesamte Team funktionieren würden.
Das ist die Geschichte, wie wir eine CDK-Projektstruktur entwarfen, die es 12 Ingenieuren ermöglichte, parallel zu arbeiten, ohne sich gegenseitig auf die Füße zu treten, die vertrauten Muster vom Serverless Framework beibehielt und zur Grundlage für unsere 2,8M Dollar ARR-Plattform wurde.
Serien-Navigation:
- Teil 1: Warum Wechseln?
- Teil 2: CDK-Umgebung Einrichten (dieser Beitrag)
- Teil 3: Lambda-Funktionen und API Gateway migrieren
- Teil 4: Datenbankressourcen und Umgebungsmanagement
- Teil 5: Authentifizierung, Autorisierung und IAM
- Teil 6: Migrationsstrategien und Best Practices
Die Projektstruktur Die Tatsächlich Skaliert#
Unser erster Versuch war eine Katastrophe. Ich kopierte naiv eine einfache CDK-Tutorial-Struktur, und innerhalb einer Woche hatten wir Merge-Konflikte, unklare Eigentumsrechte und verwirrte Ingenieure, die fragten: "Wo gehört diese Datei hin?"
Hier ist die Entwicklung vom Chaos zur Ordnung:
# Serverless Framework Struktur
my-service/
├── serverless.yml
├── package.json
├── src/
│ └── handlers/
│ ├── users.js
│ └── products.js
├── resources/
│ └── dynamodb-tables.yml
└── config/
├── dev.yml
└── prod.yml
# CDK Struktur (Nach 3 gescheiterten Versuchen)
my-service/
├── cdk.json # CDK App-Konfiguration
├── package.json
├── bin/
│ └── my-service.ts # Einzelner Entry Point (auf die harte Tour gelernt)
├── lib/
│ ├── stacks/ # Stack-Definitionen nach Domain
│ │ ├── api-stack.ts # API Gateway + Lambda-Funktionen
│ │ ├── data-stack.ts # DynamoDB-Tabellen (stateful)
│ │ └── auth-stack.ts # Cognito + Auth-Logik
│ ├── constructs/ # Wiederverwendbare Muster (unser Geheimnis)
│ │ ├── production-lambda.ts # 376 Funktionen verwenden dies
│ │ ├── api-with-auth.ts # Jede API folgt diesem Muster
│ │ └── monitored-table.ts # DynamoDB mit Alarmen
│ └── config/ # Umgebungsspezifische Configs
│ ├── development.ts
│ ├── staging.ts
│ └── production.ts
├── src/
│ └── handlers/ # Lambda-Code (vertrauter Ort)
│ ├── users/ # Nach Domain gruppiert
│ │ ├── create.ts
│ │ ├── update.ts
│ │ └── list.ts
│ └── products/
│ ├── catalog.ts
│ └── inventory.ts
└── test/
├── unit/ # Handler Unit-Tests
├── integration/ # API Integration-Tests
└── infrastructure/ # CDK Stack-Tests
Die wichtigste Erkenntnis: Domain-getriebene Organisation verhindert Merge-Konflikte, wenn 12 Ingenieure parallel arbeiten.
Dein CDK-Projekt Initialisieren#
Stell zunächst sicher, dass du die Voraussetzungen hast:
# AWS CDK CLI global installieren
npm install -g aws-cdk@2
# Installation überprüfen
cdk --version # Sollte 2.x.x zeigen
# AWS-Credentials konfigurieren (falls noch nicht geschehen)
aws configure
Erstell jetzt dein Projekt:
# Projektverzeichnis erstellen
mkdir my-serverless-api && cd my-serverless-api
# CDK mit TypeScript initialisieren
cdk init app --language typescript
# Lambda-spezifische Dependencies installieren
npm install @aws-cdk/aws-lambda-nodejs-alpha @types/aws-lambda
# Entwicklungstools installieren
npm install --save-dev esbuild @types/node ts-node
TypeScript für Lambda-Entwicklung Konfigurieren#
CDK generiert eine grundlegende tsconfig.json
. Lass uns sie für serverless Entwicklung optimieren:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./",
"baseUrl": "./",
"paths": {
"@handlers/*": ["src/handlers/*"],
"@libs/*": ["src/libs/*"],
"@constructs/*": ["lib/constructs/*"]
}
},
"include": [
"bin/**/*",
"lib/**/*",
"src/**/*",
"test/**/*"
],
"exclude": [
"cdk.out",
"node_modules"
]
}
Umgebungs-Konfigurationsmanagement#
Serverless Framework verwendet YAML-Dateien für umgebungsspezifische Konfiguration. Lassen Sie uns ein TypeScript-basiertes Äquivalent erstellen:
// lib/config/environment.ts
export interface EnvironmentConfig {
stage: string;
region: string;
account: string;
api: {
throttling: {
rateLimit: number;
burstLimit: number;
};
cors: {
origins: string[];
credentials: boolean;
};
};
lambda: {
memorySize: number;
timeout: number;
reservedConcurrentExecutions?: number;
};
monitoring: {
alarmEmail?: string;
enableXRay: boolean;
logRetentionDays: number;
};
}
// lib/config/stages/dev.ts
export const devConfig: EnvironmentConfig = {
stage: 'dev',
region: 'us-east-1',
account: '123456789012',
api: {
throttling: {
rateLimit: 100,
burstLimit: 200,
},
cors: {
origins: ['http://localhost:3000'],
credentials: true,
},
},
lambda: {
memorySize: 512,
timeout: 30,
},
monitoring: {
enableXRay: true,
logRetentionDays: 7,
},
};
// lib/config/stages/prod.ts
export const prodConfig: EnvironmentConfig = {
stage: 'prod',
region: 'us-east-1',
account: '123456789012',
api: {
throttling: {
rateLimit: 1000,
burstLimit: 2000,
},
cors: {
origins: ['https://myapp.com'],
credentials: true,
},
},
lambda: {
memorySize: 1024,
timeout: 30,
reservedConcurrentExecutions: 100,
},
monitoring: {
alarmEmail: 'alerts@myapp.com',
enableXRay: true,
logRetentionDays: 30,
},
};
// lib/config/index.ts
import { devConfig } from './stages/dev';
import { prodConfig } from './stages/prod';
export function getConfig(stage: string): EnvironmentConfig {
switch (stage) {
case 'dev':
return devConfig;
case 'prod':
return prodConfig;
default:
throw new Error(`Unbekannte Stage: ${stage}`);
}
}
Ihr Erstes Construct Erstellen#
Constructs sind CDKs Bausteine. Lassen Sie uns ein wiederverwendbares Muster für Lambda-Funktionen erstellen:
// lib/constructs/serverless-function.ts
import { Construct } from 'constructs';
import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
import { Duration } from 'aws-cdk-lib';
import { EnvironmentConfig } from '../config/environment';
export interface ServerlessFunctionProps {
entry: string;
handler?: string;
environment?: Record<string, string>;
config: EnvironmentConfig;
memorySize?: number;
timeout?: number;
}
export class ServerlessFunction extends NodejsFunction {
constructor(scope: Construct, id: string, props: ServerlessFunctionProps) {
const { config, ...functionProps } = props;
super(scope, id, {
runtime: Runtime.NODEJS_20_X,
handler: props.handler || 'handler',
entry: props.entry,
memorySize: props.memorySize || config.lambda.memorySize,
timeout: Duration.seconds(props.timeout || config.lambda.timeout),
tracing: config.monitoring.enableXRay ? Tracing.ACTIVE : Tracing.DISABLED,
environment: {
NODE_OPTIONS: '--enable-source-maps',
STAGE: config.stage,
...props.environment,
},
bundling: {
minify: config.stage === 'prod',
sourceMap: true,
sourcesContent: false,
target: 'es2022',
keepNames: true,
// AWS SDK v3 ausschließen (in Lambda Runtime bereitgestellt)
externalModules: [
'@aws-sdk/*',
],
},
reservedConcurrentExecutions: config.lambda.reservedConcurrentExecutions,
});
}
}
Ihren Ersten Stack Einrichten#
Lassen Sie uns jetzt einen Stack erstellen, der unser Construct verwendet:
// lib/stacks/api-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { RestApi, LambdaIntegration, Cors } from 'aws-cdk-lib/aws-apigateway';
import { ServerlessFunction } from '../constructs/serverless-function';
import { EnvironmentConfig } from '../config/environment';
export interface ApiStackProps extends StackProps {
config: EnvironmentConfig;
}
export class ApiStack extends Stack {
public readonly api: RestApi;
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props);
const { config } = props;
// API Gateway erstellen
this.api = new RestApi(this, 'ServerlessApi', {
restApiName: `my-service-${config.stage}`,
deployOptions: {
stageName: config.stage,
throttlingRateLimit: config.api.throttling.rateLimit,
throttlingBurstLimit: config.api.throttling.burstLimit,
},
defaultCorsPreflightOptions: {
allowOrigins: config.api.cors.origins,
allowCredentials: config.api.cors.credentials,
allowMethods: Cors.ALL_METHODS,
allowHeaders: [
'Content-Type',
'Authorization',
'X-Api-Key',
],
},
});
// Lambda-Funktionen erstellen
const createUserFn = new ServerlessFunction(this, 'CreateUserFunction', {
entry: 'src/handlers/users.ts',
handler: 'create',
config,
environment: {
// Umgebungsvariablen werden in Teil 4 hinzugefügt
},
});
// Routen einrichten
const users = this.api.root.addResource('users');
users.addMethod('POST', new LambdaIntegration(createUserFn));
}
}
CDK App Entry Point#
Aktualisieren Sie den CDK App Entry Point, um unser Konfigurationssystem zu verwenden:
// bin/my-service.ts
#!/usr/bin/env node
import 'source-map-support/register';
import { App } from 'aws-cdk-lib';
import { ApiStack } from '../lib/stacks/api-stack';
import { getConfig } from '../lib/config';
const app = new App();
// Stage aus Kontext oder Umgebung abrufen
const stage = app.node.tryGetContext('stage') || process.env.STAGE || 'dev';
const config = getConfig(stage);
new ApiStack(app, `MyServiceApiStack-${stage}`, {
config,
env: {
account: config.account,
region: config.region,
},
tags: {
Stage: stage,
Service: 'my-service',
ManagedBy: 'cdk',
},
});
Ihr Erster Lambda Handler#
Erstellen Sie einen Lambda-Handler mit TypeScript:
// src/handlers/users.ts
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
export const create = async (
event: APIGatewayProxyEventV2
): Promise<APIGatewayProxyResultV2> => {
console.log('Event:', JSON.stringify(event, null, 2));
try {
const body = JSON.parse(event.body || '{}');
// Handler-Logik hier (wird in Teil 3 erweitert)
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Benutzer erfolgreich erstellt',
stage: process.env.STAGE,
}),
};
} catch (error) {
console.error('Fehler:', error);
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
error: 'Interner Serverfehler',
}),
};
}
};
Deployment-Befehle#
Fügen Sie diese Skripte zu Ihrer package.json
hinzu:
{
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"cdk": "cdk",
"bootstrap": "cdk bootstrap",
"deploy:dev": "cdk deploy --context stage=dev",
"deploy:prod": "cdk deploy --context stage=prod",
"diff:dev": "cdk diff --context stage=dev",
"diff:prod": "cdk diff --context stage=prod",
"synth": "cdk synth",
"test": "jest",
"test:watch": "jest --watch"
}
}
Erstes Deployment#
Bootstrappen Sie Ihre AWS-Umgebung (einmalige Einrichtung):
npm run bootstrap
Deployen Sie auf Development:
npm run deploy:dev
CDK zeigt Ihnen, welche Ressourcen es zu erstellen plant. Überprüfen und bestätigen Sie.
Lokale Entwicklungsumgebung#
Anders als Serverless Frameworks serverless-offline
bietet CDK keine integrierte lokale API Gateway-Emulation. Für lokale Entwicklung haben Sie mehrere Optionen:
- SAM CLI Integration (Empfohlen):
# SAM CLI installieren
brew install aws-sam-cli # macOS
# oder folgen: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html
# CloudFormation-Template generieren
cdk synth --no-staging > template.yaml
# Lokale API starten
sam local start-api -t template.yaml
- Direktes Handler-Testing:
// test/handlers/users.test.ts
import { create } from '../../src/handlers/users';
import { APIGatewayProxyEventV2 } from 'aws-lambda';
describe('Users Handler', () => {
it('sollte einen Benutzer erstellen', async () => {
const event: Partial<APIGatewayProxyEventV2> = {
body: JSON.stringify({ name: 'John Doe' }),
};
const result = await create(event as APIGatewayProxyEventV2);
expect(result.statusCode).toBe(201);
expect(JSON.parse(result.body!)).toHaveProperty('message');
});
});
Wichtige Unterschiede Zu Beachten#
Aspekt | Serverless Framework | CDK |
---|---|---|
Konfiguration | YAML-Dateien | TypeScript-Code |
Umgebungsvariablen | ${self:provider.stage} | Config-Objekte |
Lokale Entwicklung | serverless-offline | SAM CLI oder Testing |
Deployment | serverless deploy | cdk deploy |
Ressourcenreferenzen | !Ref oder ${cf:stackName.output} | Direkte Objektreferenzen |
Was Als Nächstes#
Sie haben jetzt eine solide CDK-Grundlage, die Serverless Framework-Konventionen widerspiegelt, während sie CDKs Typsicherheit und Komponierbarkeit umarmt. Ihre Lambda-Funktionen leben an vertrauten Orten, aber Ihre Infrastruktur ist jetzt Code - echter, testbarer TypeScript-Code.
In Teil 3 werden wir Lambda-Funktionen und API Gateway-Konfigurationen migrieren, einschließlich:
- Request/Response-Transformationen
- API Gateway-Modelle und Validatoren
- Lambda-Layer und Dependencies
- Error-Handling-Muster
- API-Versionierungsstrategien
Die Grundlage ist gelegt. Lassen Sie uns Ihre serverless API bauen.
Kommentare (0)
An der Unterhaltung teilnehmen
Melde dich an, um deine Gedanken zu teilen und mit der Community zu interagieren
Noch keine Kommentare
Sei der erste, der deine Gedanken zu diesem Beitrag teilt!
Kommentare (0)
An der Unterhaltung teilnehmen
Melde dich an, um deine Gedanken zu teilen und mit der Community zu interagieren
Noch keine Kommentare
Sei der erste, der deine Gedanken zu diesem Beitrag teilt!