2025-09-04
Serverless Framework'ten AWS CDK'ya Geçiş: Bölüm 3 - DynamoDB ve S3
DynamoDB tabloları ve S3 bucket'larını CDK'ya taşıma. Data migration stratejileri ve en iyi uygulamalar.
Lambda fonksiyonları ve API Gateway konfigürasyonları, gerçek migration karmaşıklığının ortaya çıktığı yerdir. Basit bir YAML-to-TypeScript dönüşümü gibi görünen şey, hızlıca bundling optimizasyonu, memory ayarlama ve hata yönetimi pattern’lerini içeren çok katmanlı bir challenge olarak kendini gösterir.
Bu migration boyunca fonksiyon pattern’lerini standartlaştırma, cold start’ları optimize etme ve sürdürülebilir API konfigürasyonları oluşturma konularında değerli dersler öğrendim. Farklı memory ayarları, timeout konfigürasyonları ve deployment pattern’leri olan Lambda fonksiyonlarını migrate ederken öğrendiklerimi paylaşıyorum.
Seri Navigasyonu:
- Bölüm 1: Neden Geçiş Yapalım?
- Bölüm 2: CDK Environment Kurulumu
- Bölüm 3: Lambda Fonksiyonları ve API Gateway Migration (bu yazı)
- Bölüm 4: Database Kaynakları ve Environment Yönetimi
- Bölüm 5: Authentication, Authorization ve IAM
- Bölüm 6: Migration Stratejileri ve Best Practice’ler
Fonksiyon Karmaşıklığını Anlamak
Lambda fonksiyon migration’ları, gerçek bir sistemde ne kadar farklı pattern’lerin var olduğunu fark ettiğinizde hızlıca karmaşık hale gelir. Fonksiyonlar genellikle farklı gereksinimlerle çeşitli kategorilere ayrılır:
Karşılaştığım yaygın fonksiyon türleri:
- Farklı response pattern’leri olan API endpoint handler’ları
- Değişken memory ihtiyaçları olan background job processor’lar
- Hızlı response süresi gerektiren webhook handler’lar
- Farklı timeout gereksinimlerli scheduled fonksiyonlar
Her tür farklı memory ayarları, timeout konfigürasyonları ve deployment pattern’lerinden faydalanır. Bu karmaşıklık, standartlaştırılmış bir yaklaşım oluşturmanın neden önemli olduğunu gösterir.
Standartlaştırılmış Lambda Construct Oluşturmak
Birkaç fonksiyonu manuel olarak migrate ettikten ve performans sorunlarıyla karşılaştıktan sonra, standartlaştırılmış bir construct oluşturmanın değerini öğrendim. Bu yaklaşım tutarlılığı sağlamaya yardımcı olur ve kanıtlanmış optimizasyonları içerir:
# serverless.yml
functions:
getUser:
handler: src/handlers/users.get
events:
- http:
path: users/{id}
method: get
cors: true
environment:
USERS_TABLE: ${self:service}-${opt:stage}-users
timeout: 10
memorySize: 256
İşte çeşitli fonksiyon migration’larından öğrenilenlerle oluşturulmuş standartlaştırılmış construct:
// lib/constructs/production-lambda.ts
import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Runtime, Tracing, Architecture } from 'aws-cdk-lib/aws-lambda';
import { Duration, Stack } from 'aws-cdk-lib';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
export interface ProductionLambdaProps extends Omit<NodejsFunctionProps, 'runtime'> {
stage: string;
functionName: string;
// Migration deneyiminden öğrenilen performans optimizasyonları
enableProvisioning?: boolean;
enableSnapStart?: boolean;
}
export class ProductionLambda extends NodejsFunction {
constructor(scope: Construct, id: string, props: ProductionLambdaProps) {
super(scope, id, {
...props,
runtime: Runtime.NODEJS_20_X,
architecture: Architecture.ARM_64, // ARM64 daha iyi price-performance sunar
// Fonksiyon profiling'e dayalı memory optimizasyonu
memorySize: props.memorySize || ProductionLambda.getOptimalMemory(props.functionName),
// Timeout stratejisi: 28s max (API Gateway limiti 29s)
timeout: props.timeout || Duration.seconds(28),
// Tracing sadece production'da aktif
tracing: props.stage === 'prod' ? Tracing.ACTIVE : Tracing.DISABLED,
// Maliyet vs compliance için optimize edilmiş log retention
logRetention: props.stage === 'prod' ? RetentionDays.ONE_MONTH : RetentionDays.ONE_WEEK,
// Her fonksiyonun ihtiyaç duyduğu environment variable'lar
environment: {
NODE_OPTIONS: '--enable-source-maps --max-old-space-size=896',
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
STAGE: props.stage,
FUNCTION_NAME: props.functionName,
...props.environment,
},
// Cold start iyileştirmesi için bundling optimizasyonları
bundling: {
minify: props.stage === 'prod',
sourceMap: true,
sourcesContent: false,
target: 'node20',
keepNames: true,
// Daha küçük bundle boyutları için tree shaking
treeShaking: true,
// External module'ler (Lambda runtime tarafından sağlanan)
externalModules: [
'@aws-sdk/*', // Lambda runtime'da AWS SDK v3
'aws-lambda', // Lambda types
],
// Büyük fonksiyonlar için bundle analizi
metafile: props.stage !== 'prod',
// Production debugging için custom banner
banner: props.stage === 'prod'
? '/* Production Lambda - Generated by CDK */'
: undefined,
// Dead code elimination için define
define: {
'process.env.NODE_ENV': props.stage === 'prod' ? '"production"' : '"development"',
},
},
// Kritik fonksiyonlar için reserved concurrency
reservedConcurrentExecutions: props.enableProvisioning ? 10 : undefined,
});
// Tüm fonksiyonlar için standart tag'ler
Tags.of(this).add('Stage', props.stage);
Tags.of(this).add('FunctionName', props.functionName);
Tags.of(this).add('ManagedBy', 'CDK');
}
// Fonksiyon profiling'e dayalı memory optimizasyonu
private static getOptimalMemory(functionName: string): number {
// API fonksiyonları: CPU-bound, daha fazla memory'den yarar görür
if (functionName.includes('api-')) return 1024;
// Background jobs: Memory-intensive processing
if (functionName.includes('job-')) return 2048;
// Webhook'lar: Hızlı response gerekli
if (functionName.includes('webhook-')) return 512;
// Default: Balanced performance/cost
return 1024;
}
}
// Standartlaştırılmış pattern'lerle kullanım örneği
const getUserFn = new ProductionLambda(this, 'GetUserFunction', {
stage: config.stage,
functionName: 'api-get-user',
entry: 'src/handlers/users/get.ts',
handler: 'handler',
environment: {
USERS_TABLE: usersTable.tableName,
},
});
// Type-safe izinler (artık wildcard IAM policy yok)
usersTable.grantReadData(getUserFn);
// Düzgün error handling ile API Gateway entegrasyonu
const userIdResource = users.addResource('{id}');
userIdResource.addMethod('GET', new LambdaIntegration(getUserFn, {
// Düzgün error handling için integration response'lar
integrationResponses: [
{
statusCode: '200',
responseTemplates: {
'application/json': '$input.path("$")',
},
},
{
statusCode: '404',
selectionPattern: '.*"statusCode":404.*',
responseTemplates: {
'application/json': '{"error": "User not found"}',
},
},
],
}));
Lambda Layer Migration
Serverless Framework layer’ları:
layers:
shared:
path: layers/shared
compatibleRuntimes:
- nodejs20.x
functions:
createUser:
handler: src/handlers/users.create
layers:
- {Ref: SharedLambdaLayer}
Daha iyi type safety ile CDK yaklaşımı:
// lib/constructs/shared-layer.ts
import { LayerVersion, Code, Runtime } from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export class SharedLayer extends LayerVersion {
constructor(scope: Construct, id: string) {
super(scope, id, {
code: Code.fromAsset('layers/shared'),
compatibleRuntimes: [Runtime.NODEJS_20_X],
description: 'Shared utilities and dependencies',
});
}
}
// Stack'te kullanım
const sharedLayer = new SharedLayer(this, 'SharedLayer');
const createUserFn = new ServerlessFunction(this, 'CreateUserFunction', {
entry: 'src/handlers/users.ts',
handler: 'create',
config,
layers: [sharedLayer],
});
Function Bundling ve Dependency’ler
CDK’nın NodejsFunction’ı gelişmiş bundling seçenekleri sağlıyor:
// lib/constructs/optimized-function.ts
export class OptimizedFunction extends ServerlessFunction {
constructor(scope: Construct, id: string, props: ServerlessFunctionProps) {
super(scope, id, {
...props,
bundling: {
minify: props.config.stage === 'prod',
sourceMap: true,
sourcesContent: false,
target: 'es2022',
keepNames: true,
// External module'ler (bundle edilmez)
externalModules: [
'@aws-sdk/*', // Lambda runtime tarafından sağlanan AWS SDK v3
'aws-lambda', // Sadece type'lar
],
// Belirli module'leri zorla dahil et
nodeModules: ['bcrypt', 'sharp'], // Native dependency'ler
// Build environment
environment: {
NODE_ENV: props.config.stage === 'prod' ? 'production' : 'development',
},
// Custom esbuild plugin'leri
esbuildArgs: {
'--log-level': 'warning',
'--tree-shaking': 'true',
},
},
});
}
}
API Gateway Gelişmiş Konfigürasyonları
Request Validation
Serverless Framework request validation:
functions:
createUser:
handler: src/handlers/users.create
events:
- http:
path: users
method: post
request:
schemas:
application/json: ${file(schemas/create-user.json)}
Inline model’ler ve validator’lar ile CDK:
// lib/constructs/validated-api.ts
import {
RestApi,
Model,
JsonSchema,
JsonSchemaType,
RequestValidator,
MethodOptions
} from 'aws-cdk-lib/aws-apigateway';
export class ValidatedApi extends RestApi {
private validator: RequestValidator;
constructor(scope: Construct, id: string, props: RestApiProps) {
super(scope, id, props);
// Reusable validator oluştur
this.validator = new RequestValidator(this, 'BodyValidator', {
restApi: this,
validateRequestBody: true,
validateRequestParameters: false,
});
}
addValidatedMethod(
resource: IResource,
httpMethod: string,
integration: Integration,
schema: JsonSchema
): Method {
// Schema'dan model oluştur
const model = new Model(this, `${httpMethod}${resource.path}Model`, {
restApi: this,
contentType: 'application/json',
schema,
});
// Validation ile method ekle
return resource.addMethod(httpMethod, integration, {
requestValidator: this.validator,
requestModels: {
'application/json': model,
},
});
}
}
// Kullanım
const createUserSchema: JsonSchema = {
type: JsonSchemaType.OBJECT,
required: ['email', 'name'],
properties: {
email: {
type: JsonSchemaType.STRING,
format: 'email',
},
name: {
type: JsonSchemaType.STRING,
minLength: 1,
maxLength: 100,
},
age: {
type: JsonSchemaType.INTEGER,
minimum: 0,
maximum: 150,
},
},
};
api.addValidatedMethod(
users,
'POST',
new LambdaIntegration(createUserFn),
createUserSchema
);
Response Transformations
Serverless Framework response template’leri:
functions:
getUsers:
handler: src/handlers/users.list
events:
- http:
path: users
method: get
response:
headers:
Content-Type: "'application/json'"
template: $input.path(')
statusCodes:
200:
pattern: ''
404:
pattern: '.*"statusCode":404.*'
template: $input.path('$.errorMessage')
CDK integration response konfigürasyonu:
// lib/constructs/api-integration.ts
export function createLambdaIntegration(
fn: IFunction,
options?: {
enableCors?: boolean;
responseMapping?: Record<string, IntegrationResponse>;
}
): LambdaIntegration {
const responseParameters: Record<string, string> = {};
if (options?.enableCors) {
responseParameters['method.response.header.Access-Control-Allow-Origin'] = "'*'";
}
return new LambdaIntegration(fn, {
proxy: false,
integrationResponses: [
{
statusCode: '200',
responseParameters,
responseTemplates: {
'application/json': '$input.path("$")',
},
},
{
statusCode: '404',
selectionPattern: '.*"statusCode":404.*',
responseParameters,
responseTemplates: {
'application/json': '$input.path("$.errorMessage")',
},
},
{
statusCode: '500',
selectionPattern: '.*"statusCode":5\\d{2}.*',
responseParameters,
responseTemplates: {
'application/json': '{"error": "Internal Server Error"}',
},
},
],
});
}
API Gateway Authorizer’lar
Serverless Framework authorizer’larından migration:
functions:
auth:
handler: src/handlers/auth.handler
getProfile:
handler: src/handlers/users.profile
events:
- http:
path: users/profile
method: get
authorizer: auth
CDK Lambda authorizer implementasyonu:
// lib/constructs/api-authorizer.ts
import {
TokenAuthorizer,
IdentitySource,
IRestApi
} from 'aws-cdk-lib/aws-apigateway';
import { Duration } from 'aws-cdk-lib';
export class ApiAuthorizer extends TokenAuthorizer {
constructor(scope: Construct, id: string, props: {
api: IRestApi;
authorizerFunction: IFunction;
}) {
super(scope, id, {
restApi: props.api,
handler: props.authorizerFunction,
identitySource: IdentitySource.header('Authorization'),
resultsCacheTtl: Duration.minutes(5),
authorizerName: `${props.api.restApiName}-authorizer`,
});
}
}
// Stack'te kullanım
const authFn = new ServerlessFunction(this, 'AuthorizerFunction', {
entry: 'src/handlers/auth.ts',
handler: 'handler',
config,
});
const authorizer = new ApiAuthorizer(this, 'ApiAuthorizer', {
api: this.api,
authorizerFunction: authFn,
});
// Korumalı endpoint
const profile = users.addResource('profile');
profile.addMethod('GET', new LambdaIntegration(getProfileFn), {
authorizer,
authorizationType: AuthorizationType.CUSTOM,
});
Error Handling Pattern’leri
Structured Error Response’lar
Sağlam bir error handling sistemi oluşturun:
// src/libs/api-gateway.ts
export class ApiError extends Error {
constructor(
public statusCode: number,
message: string,
public code?: string
) {
super(message);
this.name = 'ApiError';
}
}
export const formatError = (error: unknown): APIGatewayProxyResultV2 => {
console.error('Error:', error);
if (error instanceof ApiError) {
return {
statusCode: error.statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: {
code: error.code || 'UNKNOWN_ERROR',
message: error.message,
},
}),
};
}
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: {
code: 'INTERNAL_SERVER_ERROR',
message: 'An unexpected error occurred',
},
}),
};
};
// src/libs/lambda.ts
export const withErrorHandling = <T extends (...args: any[]) => any>(
handler: T
): T => {
return (async (...args: Parameters<T>) => {
try {
return await handler(...args);
} catch (error) {
return formatError(error);
}
}) as T;
};
Handler’larda Error Handling Kullanımı
// src/handlers/users.ts
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
import { ApiError, withErrorHandling } from '../libs/api-gateway';
export const get = withErrorHandling(
async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> => {
const { id } = event.pathParameters || {};
if (!id) {
throw new ApiError(400, 'User ID is required', 'MISSING_PARAMETER');
}
// Database lookup simülasyonu
const user = await getUserById(id);
if (!user) {
throw new ApiError(404, 'User not found', 'USER_NOT_FOUND');
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user }),
};
}
);
API Versioning Stratejileri
Path-Based Versioning
// lib/stacks/versioned-api-stack.ts
export class VersionedApiStack extends Stack {
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props);
const api = new RestApi(this, 'VersionedApi', {
restApiName: `my-service-${props.config.stage}`,
});
// Version 1
const v1 = api.root.addResource('v1');
this.setupV1Routes(v1, props.config);
// Breaking change'ler ile Version 2
const v2 = api.root.addResource('v2');
this.setupV2Routes(v2, props.config);
}
private setupV1Routes(parent: IResource, config: EnvironmentConfig) {
const users = parent.addResource('users');
// Legacy response format
const getUserV1Fn = new ServerlessFunction(this, 'GetUserV1Function', {
entry: 'src/handlers/v1/users.ts',
handler: 'get',
config,
});
users.addResource('{id}').addMethod('GET',
new LambdaIntegration(getUserV1Fn)
);
}
private setupV2Routes(parent: IResource, config: EnvironmentConfig) {
const users = parent.addResource('users');
// Pagination ile yeni response format
const getUserV2Fn = new ServerlessFunction(this, 'GetUserV2Function', {
entry: 'src/handlers/v2/users.ts',
handler: 'get',
config,
});
users.addResource('{id}').addMethod('GET',
new LambdaIntegration(getUserV2Fn)
);
}
}
Performans Optimizasyonları
Lambda Cold Start Optimizasyonu
// lib/constructs/warm-function.ts
import { Rule, Schedule } from 'aws-cdk-lib/aws-events';
import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets';
export class WarmFunction extends ServerlessFunction {
constructor(scope: Construct, id: string, props: ServerlessFunctionProps & {
warmingSchedule?: Schedule;
}) {
super(scope, id, props);
if (props.config.stage === 'prod' && props.warmingSchedule) {
// Warming rule oluştur
new Rule(this, 'WarmingRule', {
schedule: props.warmingSchedule,
targets: [
new LambdaFunction(this, {
event: {
source: 'warmer',
action: 'ping',
},
}),
],
});
// Handler'a warming check ekle
this.addEnvironment('ENABLE_WARMING', 'true');
}
}
}
// Handler'da
export const handler = async (event: any) => {
// Warming invocation'larını atla
if (event.source === 'warmer') {
return { statusCode: 200, body: 'Warmed' };
}
// Normal handler logic
};
API Gateway Caching
// lib/constructs/cached-method.ts
export function addCachedMethod(
resource: IResource,
httpMethod: string,
integration: Integration,
cachingEnabled: boolean = true,
ttl: Duration = Duration.minutes(5)
): Method {
return resource.addMethod(httpMethod, integration, {
methodResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Cache-Control': true,
},
}],
requestParameters: {
'method.request.querystring.page': false,
'method.request.querystring.limit': false,
},
});
}
// Stage level'da caching aktif et
const api = new RestApi(this, 'CachedApi', {
deployOptions: {
cachingEnabled: true,
cacheClusterEnabled: true,
cacheClusterSize: '0.5',
cacheTtl: Duration.minutes(5),
cacheDataEncrypted: true,
},
});
Migration Checklist
Production’a geçmeden önce şunları ele aldığınızdan emin olun:
- Tüm Lambda fonksiyonları uygun memory/timeout ayarlarıyla migrate edildi
- Environment variable’lar düzgün scope’lanmış ve şifrelenmiş
- API Gateway route’ları mevcut path’lerle tam olarak eşleşiyor
- CORS konfigürasyonu mevcut ayarlarla eşleşiyor
- Request validation schema’ları migrate edildi
- Custom authorizer’lar implement edildi ve test edildi
- Error response’lar backward compatibility’yi koruyor
- Lambda layer’lar düzgün konfigüre edildi
- Cold start optimizasyonları yerinde
- API caching stratejisi implement edildi
- Monitoring ve alarm’lar konfigüre edildi
Öğrenilen Temel Dersler
Bu migration deneyimi boyunca önemli birkaç pattern ortaya çıktı:
Standardizasyon Karşılığını Verir
Tutarlı bir fonksiyon construct’ı oluşturmak, konfigürasyon drift’ini ortadan kaldırır ve performans optimizasyonlarını otomatik hale getirir. Yeni fonksiyonlar özel konfigürasyon gerektirmek yerine kanıtlanmış pattern’leri miras alır.
Memory ve Architecture Seçimleri Önemlidir
ARM64 architecture ve doğru boyutlandırılmış memory allocation hem performansı hem de maliyeti önemli ölçüde etkileyebilir. Farklı fonksiyon türleri farklı memory konfigürasyonlarından faydalanır.
Bundling Stratejisi Kritiktir
Tree shaking ve external module exclusion ile düşünceli bundling, cold start sürelerini azaltır. AWS SDK v3 Lambda runtime’da mevcut olduğu için bundle’lardan çıkarmak yardımcı olur.
Error Handling Yapı Gerektirir
API Gateway error handling dikkatli integration response konfigürasyonu gerektirir. Tüm fonksiyonlarda tutarlı error response pattern’lerine sahip olmak debugging ve client handling’i iyileştirir.
Sonraki Adımlar: Database ve Environment Yönetimi
Lambda fonksiyonları ve API Gateway konfigürasyonları migrate edildiğinde, sonraki challenge database kaynakları ve environment yönetimini içerir. Stateless fonksiyonların aksine, database’ler kalıcı veri içerdikleri için dikkatli handling gerektirir çünkü kolayca yeniden oluşturulamazlar.
4. Bölüm’de şunları keşfedeceğiz:
- DynamoDB tabloları ve RDS instance’larını migrate etmek
- Environment variable yönetimi ve secret handling
- Database erişimi için VPC konfigürasyonları
- Backup ve disaster recovery stratejileri
- Cross-environment tutarlılık pattern’leri
Database migration, özellikle data güvenliği ve environment izolasyonu konularında, function migration’dan farklı stratejiler gerektirir.
Serverless Framework'ten AWS CDK'ya Geçiş Rehberi
Serverless Framework'ten AWS CDK'ya tam geçiş sürecini kapsayan 6 bölümlük kapsamlı rehber. Kurulum, uygulama pattern'leri ve best practice'ler dahil.
Serideki tüm yazılar
İlgili yazılar
Amazon Cognito'nun gelişmiş özellikleri üzerine kapsamlı teknik kılavuz: özel authentication akışları, federation pattern'leri, multi-tenancy mimarileri, migration stratejileri ve production-grade güvenlik implementasyonu.
AWS CDK, Lambda ve GitHub Actions kullanarak otomatik preview ortamları oluşturmayı öğrenin - sorunsuz PR test ve inceleme süreçleri için
Global uygulamalar için AWS edge computing çözümlerini seçme ve uygulama üzerine pratik örnekler ve maliyet optimizasyonu stratejileri içeren kapsamlı teknik rehber.
AWS Secrets Manager ve Systems Manager Parameter Store'u karşılaştıran kapsamlı teknik rehber - hangi servisi ne zaman kullanmalı ve gerçek dünya implementation pattern'leri.
Native AWS servisleri, otomasyon ve kanıtlanmış implementation pattern'leri kullanarak AWS maliyetlerini %40-70 azaltmaya yönelik kapsamlı bir rehber.