2025-09-04
AWS Serverless TypeScript Projelerini Yapılandırma: Eksiksiz Rehber
AWS Lambda, API Gateway ve TypeScript ile production-ready serverless projeleri oluşturmak için en iyi uygulamalar. Gerçek dünya örnekleri, maliyet optimizasyonu ve performans ipuçları.
EC2 üzerinde çalışan geleneksel bir Express.js API’si sabit maliyet, öngörülebilir ölçeklendirme ve %99.9 uptime sunar. Lambda’ya geçiş kararı genellikle belirli bir uyumsuzluktan doğar: ayda bir kez, 10 dakikadan kısa sürede 50.000 webhook işlemesi gereken bir özellik.
Aylık 10 dakikalık bir yoğunluk için EC2 instance’larını 7/24 çalışır tutmak israftır. Lambda bunu doğrudan çözer. Aşağıdaki kalıplar production Lambda fonksiyonlarını, yaygın serverless hatalarını ve AWS faturalarını düşüren TypeScript yaklaşımlarını kapsar.
Serverless’ı Benimsemek: Yaygın Direnç Yayı
Serverless’a karşı standart itiraz “ekstra adımlarla vendor lock-in” biçimindedir. Kubernetes cluster’ları yönetmekte ve JVM garbage collector’larını ince ayarlamakta rahat olan ekipler, Lambda’yı kontrolü bırakmak olarak görür. Üç tekrar eden senaryo bu görüşü değiştirme eğilimindedir:
Beklenmedik Trafik Artışı
Büyük bir teknoloji link toplayıcısında yer alan bir Express API, trafiğin bir gecede dakikada 100 istekten dakikada 5.000 isteğe sıçradığını görebilir. Auto-scaling grupları yeni instance’ları başlatmak için tipik olarak 6-10 dakikaya ihtiyaç duyar. Bu pencerede ödeme işleme hataları birikir ve Redis cache’leri aşırı yüklenir.
Lambda anında ölçeklenir. Bu tür bir incident, otomatik ölçeklendirmenin değerini ortaya koyar.
Webhook İşleme Zorluğu
10.000’den fazla event’in patlamalar halinde geldiği Stripe webhook’larını işlemek, EC2’nin iki kötü seçeneğini açığa çıkarır:
- Tepe yük için fazla provision (pahalı)
- Queue kullan ve webhook timeout riski al (güvenilmez)
Lambda’nın otomatik concurrency ölçeklendirmesi bunu zarif biçimde çözdü. Her webhook kendi fonksiyon instance’ını aldı. Queue yok, timeout yok, fazla provisioning yok.
Compute Kullanım Analizi
API sunucusu kullanım analizi sıklıkla sunucuların zamanın %87’sinde boşta olduğunu, ancak %100 kapasite için ödeme yapıldığını ortaya koyar. Kullanılmayan kaynakların aylık maliyeti önemlidir.
Lambda’nın milisaniye başına ödeme modeli bu verimsizliği doğrudan ele alır.
Production’da Gerçekten İşe Yarayan Stack
Birden fazla yaklaşım değerlendirildikten sonra, işte production’da kanıtlanmış bir CDK stack’i:
// Production CDK stack
import { Stack, StackProps, Duration, RemovalPolicy } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { RestApi, LambdaIntegration, Cors, MethodLoggingLevel } from 'aws-cdk-lib/aws-apigateway';
import { Table, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
import { Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
export class ProductionServerlessStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// DynamoDB tablosu - single-table design
const dataTable = new Table(this, 'DataTable', {
partitionKey: { name: 'PK', type: AttributeType.STRING },
sortKey: { name: 'SK', type: AttributeType.STRING },
billingMode: BillingMode.PAY_PER_REQUEST, // On-demand fiyatlama trafik spike'larini karsilar
// Point-in-time recovery kazara silmeye karsi korur
pointInTimeRecovery: true,
removalPolicy: RemovalPolicy.RETAIN, // Prod verisini asla yanlislikla silme
});
// Farkli erisim pattern'leri icin GSI ekle
dataTable.addGlobalSecondaryIndex({
indexName: 'GSI1',
partitionKey: { name: 'GSI1PK', type: AttributeType.STRING },
sortKey: { name: 'GSI1SK', type: AttributeType.STRING },
});
// Production'a hazir ayarlarla Lambda fonksiyonu
const apiHandler = new NodejsFunction(this, 'ApiHandler', {
entry: 'src/handlers/api.ts',
runtime: Runtime.NODEJS_20_X,
// Tahmin degil, gercek profiling'e dayali memory boyutlandirma
memorySize: 1024, // JSON isleme workload'umuz icin sweet spot
timeout: Duration.seconds(28), // API Gateway'in 29s limitinin hemen altinda
environment: {
TABLE_NAME: dataTable.tableName,
NODE_ENV: 'production',
// DynamoDB icin connection reuse'u etkinlestir
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
// Ozel env vars
LOG_LEVEL: 'info',
ENABLE_X_RAY: 'true',
},
bundling: {
minify: true,
target: 'node20',
// aws-sdk'yi bundle'dan haric tut - Lambda runtime sagliyor
externalModules: ['@aws-sdk/*'],
// Kullanilmayan kodu tree-shake et
treeShaking: true,
// Prod sorunlarini debug etmek icin source maps
sourceMap: true,
// Dead code elimination icin define
define: {
'process.env.NODE_ENV': '"production"',
},
},
// Debug icin X-Ray tracing'i etkinlestir
tracing: Tracing.ACTIVE,
// Lambda'nin tum hesap limitini tuketmesini onlemek icin reserved concurrency
reservedConcurrentExecutions: 100,
});
// DynamoDB izinlerini ver
dataTable.grantReadWriteData(apiHandler);
// Dogru CORS ve throttling ile API Gateway
const api = new RestApi(this, 'ServerlessApi', {
restApiName: 'production-serverless-api',
description: 'Production serverless API with proper error handling',
defaultCorsPreflightOptions: {
allowOrigins: process.env.NODE_ENV === 'production'
? ['https://yourdomain.com']
: Cors.ALL_ORIGINS,
allowMethods: Cors.ALL_METHODS,
allowHeaders: ['Content-Type', 'Authorization', 'X-Amz-Date'],
},
deployOptions: {
// Stage'e ozel throttling
throttlingRateLimit: 1000,
throttlingBurstLimit: 2000,
// Detayli CloudWatch metriklerini etkinlestir
metricsEnabled: true,
loggingLevel: MethodLoggingLevel.INFO,
// X-Ray tracing'i etkinlestir
tracingEnabled: true,
},
});
// Dogru integration ile resource ekle
const items = api.root.addResource('items');
items.addMethod('GET', new LambdaIntegration(apiHandler));
items.addMethod('POST', new LambdaIntegration(apiHandler));
const singleItem = items.addResource('{id}');
singleItem.addMethod('GET', new LambdaIntegration(apiHandler));
singleItem.addMethod('PUT', new LambdaIntegration(apiHandler));
singleItem.addMethod('DELETE', new LambdaIntegration(apiHandler));
}
}
Gerçeği Ele Alan Lambda Handler
İşte yaygın production hata kalıplarından türetilmiş error handling ve optimizasyonlar içeren bir production Lambda handler’ı:
// src/handlers/api.ts
import { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand, PutCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';
// Connection reuse icin DynamoDB client'i handler disinda olustur
const dynamoClient = new DynamoDBClient({
region: process.env.AWS_REGION,
// Maliyet verimliligi icin connection pooling ayarlari
maxAttempts: 3,
requestHandler: {
connectionTimeout: 1000,
socketTimeout: 1000,
},
});
const docClient = DynamoDBDocumentClient.from(dynamoClient, {
marshallOptions: {
removeUndefinedValues: true, // DynamoDB validation hatalarini onler
convertEmptyValues: false,
},
});
interface Item {
id: string;
name: string;
description?: string;
createdAt: string;
updatedAt: string;
}
// Yuksek hacimli istekleri isleyen handler
export const handler: APIGatewayProxyHandler = async (event): Promise<APIGatewayProxyResult> => {
// Performans optimizasyonu: bir kez parse et, her yerde kullan
const { httpMethod, pathParameters, body, requestContext } = event;
const requestId = requestContext.requestId;
// Incident sirasinda gercekten yardimci olan yapilandirilmis loglama
console.log('Request received', {
requestId,
method: httpMethod,
path: event.path,
pathParams: pathParameters,
userAgent: event.headers['User-Agent'],
sourceIp: event.requestContext.identity.sourceIp,
});
try {
switch (httpMethod) {
case 'GET':
return await handleGet(pathParameters?.id, requestId);
case 'POST':
return await handlePost(body, requestId);
case 'PUT':
return await handlePut(pathParameters?.id, body, requestId);
case 'DELETE':
return await handleDelete(pathParameters?.id, requestId);
default:
return createResponse(405, { error: 'Method not allowed' });
}
} catch (error) {
// Production incident'larindan gecmis error handling
console.error('Handler error', {
requestId,
error: error.message,
stack: error.stack,
// Temizlenmis istek verisi (hassas bilgiyi asla loglama)
method: httpMethod,
path: event.path,
});
// Hata tipine gore farkli hata yanitlari
if (error.name === 'ValidationException') {
return createResponse(400, { error: 'Invalid request data' });
}
if (error.name === 'ConditionalCheckFailedException') {
return createResponse(409, { error: 'Resource conflict' });
}
if (error.name === 'ResourceNotFoundException') {
return createResponse(404, { error: 'Resource not found' });
}
// Beklenmeyen sorunlar icin genel sunucu hatasi
return createResponse(500, {
error: 'Internal server error',
requestId, // Destek talepleri icin ekle
});
}
};
async function handleGet(id: string | undefined, requestId: string): Promise<APIGatewayProxyResult> {
if (!id) {
// Tum item'lari pagination ile listele
const result = await docClient.send(new QueryCommand({
TableName: process.env.TABLE_NAME!,
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': 'ITEM',
},
Limit: 50, // Timeout'a yol acan buyuk scan'leri onle
}));
const items = result.Items?.map(item => ({
id: item.SK.replace('ITEM#', ''),
name: item.name,
description: item.description,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
})) || [];
return createResponse(200, { items, count: items.length, requestId });
}
// Tek item al
const result = await docClient.send(new GetCommand({
TableName: process.env.TABLE_NAME!,
Key: {
PK: 'ITEM',
SK: `ITEM#${id}`,
},
}));
if (!result.Item) {
return createResponse(404, { error: 'Item not found', requestId });
}
const item: Item = {
id: result.Item.SK.replace('ITEM#', ''),
name: result.Item.name,
description: result.Item.description,
createdAt: result.Item.createdAt,
updatedAt: result.Item.updatedAt,
};
return createResponse(200, { item, requestId });
}
async function handlePost(body: string | null, requestId: string): Promise<APIGatewayProxyResult> {
if (!body) {
return createResponse(400, { error: 'Request body is required', requestId });
}
let data: Partial<Item>;
try {
data = JSON.parse(body);
} catch (error) {
return createResponse(400, { error: 'Invalid JSON', requestId });
}
// Bircok production hatasini onleyen validation
if (!data.name || typeof data.name !== 'string' || data.name.trim().length === 0) {
return createResponse(400, { error: 'Name is required and must be a non-empty string', requestId });
}
if (data.name.length > 100) {
return createResponse(400, { error: 'Name must be 100 characters or less', requestId });
}
const id = generateId(); // Ozel ID uretimi
const now = new Date().toISOString();
const item: Item = {
id,
name: data.name.trim(),
description: data.description?.trim() || undefined,
createdAt: now,
updatedAt: now,
};
// Composite key'lerle single-table design
await docClient.send(new PutCommand({
TableName: process.env.TABLE_NAME!,
Item: {
PK: 'ITEM',
SK: `ITEM#${id}`,
...item,
// Alternatif erisim pattern'leri icin GSI key'leri
GSI1PK: 'ITEMS_BY_NAME',
GSI1SK: item.name.toLowerCase(),
},
// Mevcut item'larin uzerine yazmayi onle
ConditionExpression: 'attribute_not_exists(PK)',
}));
console.log('Item created', { requestId, itemId: id });
return createResponse(201, { item, requestId });
}
// Tutarli yanitlar icin utility fonksiyonu
function createResponse(statusCode: number, body: any): APIGatewayProxyResult {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // Production icin ayarla
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
'X-Request-ID': body.requestId || 'unknown',
},
body: JSON.stringify(body),
};
}
// URL-safe benzersiz ID'ler uret
function generateId(): string {
return `${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}`;
}
Binlerce Dolar Tasarruf Sağlayan Maliyet Optimizasyon Dersleri
1. Memory vs. CPU Dengeleri
Memory optimizasyonu, açık olmayan dengeleri ortaya çıkarır:
// Memory profiling sasirtici icgoruler ortaya cikardi
// Not: Bunlar tipik workload'lara dayali ornek hesaplamalardir - maliyetleriniz degisebilir
const memoryConfigs = [
{ memory: 512, avgDuration: 850, avgCost: 0.0012 }, // CPU-bound
{ memory: 1024, avgDuration: 420, avgCost: 0.0009 }, // Sweet spot
{ memory: 1536, avgDuration: 380, avgCost: 0.0011 }, // Azalan getiri
{ memory: 3008, avgDuration: 360, avgCost: 0.0021 }, // Asiri provision
];
1024 MB sıklıkla sweet spot’tur. Daha fazla memory = daha hızlı execution = daha az maliyet; bir noktaya kadar.
2. Connection Reuse: Maliyet Etkisi
// Once: Her invocation'da yeni connection = pahali
const dynamoClient = new DynamoDBClient({ region: 'us-east-1' });
// Sonra: Connection reuse = %15 maliyet dususu
const dynamoClient = new DynamoDBClient({
region: 'us-east-1',
maxAttempts: 3,
requestHandler: {
connectionTimeout: 1000,
socketTimeout: 1000,
},
});
// HTTP keep-alive'i etkinlestir
process.env.AWS_NODEJS_CONNECTION_REUSE_ENABLED = '1';
3. Bundle Size Optimizasyonu
// Cold start'lari %40 azaltan CDK bundling yapilandirmasi
bundling: {
minify: true,
target: 'node20',
externalModules: [
'@aws-sdk/*', // Lambda runtime surumunu kullan
'aws-lambda', // Zaten mevcut
],
treeShaking: true,
sourceMap: process.env.NODE_ENV !== 'production', // Debug bilgisi sadece dev'de
define: {
'process.env.NODE_ENV': '"production"',
},
banner: '/* Production Lambda bundle */',
// Kritik: buyuk bagimliliklari haric tut
nodeModules: {
// Sadece gercekten kullandigimizi bundle'la
'lodash': {
include: ['throttle', 'debounce'], // Kullanilmayan fonksiyonlari tree-shake et
},
},
}
4. CloudWatch Logs Hacmi
CloudWatch Logs alımı gigabayt başına faturalanır. Yüksek hacimli info loglaması faturayı tek başına şişirebilir. LOG_LEVEL üzerinden anahtarlanan yapısal bir logger, hata ve uyarıları her zaman görünür tutarken production ortamında ayrıntılı info çıktısını bastırır:
// Error ve warn her zaman yazılır; info yalnızca info/debug seviyesinde, böylece LOG_LEVEL=warn onu susturur
const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 } as const;
const threshold = LEVELS[(process.env.LOG_LEVEL as keyof typeof LEVELS) ?? 'info'] ?? LEVELS.info;
const logger = {
error: (message: string, meta?: any) => {
console.error(JSON.stringify({ level: 'error', message, meta, timestamp: new Date().toISOString() }));
},
warn: (message: string, meta?: any) => {
console.warn(JSON.stringify({ level: 'warn', message, meta, timestamp: new Date().toISOString() }));
},
info: (message: string, meta?: any) => {
if (threshold >= LEVELS.info) {
console.log(JSON.stringify({ level: 'info', message, meta, timestamp: new Date().toISOString() }));
}
},
};
5. DynamoDB Faturalama Modu
Faturalama modu, trafik şekline bağlı bir maliyet kaldıracıdır. On-demand (PAY_PER_REQUEST) öngörülemeyen ani yükleri kapasite planlaması olmadan karşılar. Provisioned kapasite ise istikrarlı ve öngörülebilir throughput için daha ucuzdur:
// Write-heavy, spike'li workload'lar icin on-demand
const writeHeavyTable = new Table(this, 'WriteHeavyTable', {
billingMode: BillingMode.PAY_PER_REQUEST, // Spike'larda maliyet etkili
});
// Ongorulebilir workload'lar icin provisioned
const predictableTable = new Table(this, 'PredictableTable', {
billingMode: BillingMode.PROVISIONED,
readCapacity: 5,
writeCapacity: 5,
});
Gerçek Sorunlarda Uyarı Veren Monitoring Kurulumu
Production monitoring, alarmlar gürültüyü değil gerçek sorunları hedeflediğinde en iyi şekilde çalışır:
// Yalanci alarm vermeyen CloudWatch alarmlari
import { Alarm, Metric, TreatMissingData } from 'aws-cdk-lib/aws-cloudwatch';
import { Function } from 'aws-cdk-lib/aws-lambda';
export class ServerlessMonitoring extends Construct {
constructor(scope: Construct, id: string, props: { lambdaFunction: Function }) {
super(scope, id);
// Hata orani alarmi - 5 dakikada %5 hata orani
const errorAlarm = new Alarm(this, 'HighErrorRate', {
metric: props.lambdaFunction.metricErrors({
statistic: 'Sum',
period: Duration.minutes(5),
}).with({
statistic: 'Average',
}),
threshold: 0.05, // %5 hata orani
evaluationPeriods: 2,
treatMissingData: TreatMissingData.NOT_BREACHING,
});
// Sure alarmi - 95. persentil 5 saniyenin uzerinde
const durationAlarm = new Alarm(this, 'SlowRequests', {
metric: props.lambdaFunction.metricDuration({
statistic: 'p95',
period: Duration.minutes(5),
}),
threshold: 5000, // 5 saniye
evaluationPeriods: 3,
});
// Throttle alarmi - herhangi bir throttling kotudur
const throttleAlarm = new Alarm(this, 'ThrottledRequests', {
metric: props.lambdaFunction.metricThrottles({
statistic: 'Sum',
period: Duration.minutes(1),
}),
threshold: 1,
evaluationPeriods: 1,
});
// Is mantigi hatalari icin ozel metrik
const businessErrorAlarm = new Alarm(this, 'BusinessLogicErrors', {
metric: new Metric({
namespace: 'MyApp/Lambda',
metricName: 'BusinessErrors',
statistic: 'Sum',
}),
threshold: 10,
evaluationPeriods: 2,
});
}
}
Yaygın Production Hataları
1. Concurrent Execution Limit Sorunu
Yüksek trafikli bir olay sırasında webhook işleyen Lambda’lar, bir AWS hesabındaki 1.000 concurrent execution’ın tamamını tüketebilir. Ana API ise Lambda kapasitesi alamadığı için kesinti yaşar.
Çözüm: Kritik fonksiyonlarda reserved concurrency ayarla:
reservedConcurrentExecutions: 100, // Kapasiteyi garanti et
2. DynamoDB Hot Partition Sorunu
DynamoDB partition key’leri için sıralı ID’ler kullanmak tüm trafiğin tek bir partition’a gitmesine neden oldu. Read/write throttling performansı belirgin biçimde düşürdü.
Çözüm: Dağıtılmış partition key’leri:
// Kotu: Sirali ID'ler hot partition olusturur
PK: `USER#${sequentialId}`
// Iyi: UUID veya timestamp + random
PK: `USER#${uuid.v4()}`
// Veya: Zaman bazli erisim icin mevcut saat + random kullan
PK: `USER#${new Date().getHours()}-${Math.random().toString(36)}`
3. 15 Dakikalık Timeout Keşfi
Lambda fonksiyonları tam 15 dakika sonra timeout oluyordu. İlk başta bir memory leak’ten şüphelenildi, ancak AWS’in 15 dakikalık maksimum execution süresi limiti olduğu keşfedildi. Büyük batch’ler senkron olarak işleniyordu.
Çözüm: Pagination ile batch işleme:
// Daha kucuk parcalar halinde isle
const BATCH_SIZE = 100;
const MAX_EXECUTION_TIME = 14 * 60 * 1000; // 14 dakika
const startTime = Date.now();
for (let i = 0; i < items.length; i += BATCH_SIZE) {
if (Date.now() - startTime > MAX_EXECUTION_TIME) {
// SQS uzerinden devami planla
await scheduleRemainingWork(items.slice(i));
break;
}
const batch = items.slice(i, i + BATCH_SIZE);
await processBatch(batch);
}
4. DynamoDB Scan Maliyet Tuzağı
Scan tüm tabloyu okur ve yalnızca eşleşen birkaç kayıt için değil, incelenen her kayıt için faturalandırır. Büyük bir tabloda bu, kayda değer ve tekrar eden bir maliyete dönüşür. Global Secondary Index artı Query, yalnızca eşleşen partition’ı okur:
// Bu kod önemli maliyetlere neden oldu
const getAllUsers = async () => {
const result = await docClient.send(new ScanCommand({
TableName: process.env.TABLE_NAME,
}));
return result.Items; // 2M kayit scan'ledi
};
// Duzeltme: Query kullan
const getUsersByStatus = async (status: string) => {
const result = await docClient.send(new QueryCommand({
TableName: process.env.TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': `STATUS#${status}`,
},
}));
return result.Items;
};
5. Lambda Memory Leak
Sıcak bir Lambda container’ı, modül-global state’i çağrılar arasında yeniden kullanır. Modül seviyesindeki bir nesneye eklenen her şey, container geri dönüştürülene kadar sınırsız büyür ve sonunda belleği tüketir. Request kapsamlı state ise tek bir çağrıyla birlikte yaşar ve sona erer:
// Yanlis - global degiskenlerde veri biriktirme
let cache: any = {}; // Bu Lambda instance'larinda memory leak'e neden olur
export const handler = async (event: APIGatewayProxyEvent) => {
cache[event.requestContext.requestId] = event; // Memory leak
// ...
};
// Dogru - her request icin temiz state
export const handler = async (event: APIGatewayProxyEvent) => {
const requestCache = new Map(); // Local scope
// ...
};
Production Güvenilirliği için TypeScript Kalıpları
1. Katı Event Tip Tanımları
// Daha iyi IntelliSense icin ozel tip tanimlari
interface StrictAPIGatewayEvent extends APIGatewayProxyEvent {
pathParameters: { [key: string]: string }; // Bizim kurulumumuzda asla null degil
body: string; // POST/PUT icin her zaman mevcut
}
// Runtime guvenligi icin type guard'lar
function isValidItemData(data: any): data is Partial<Item> {
return typeof data === 'object' &&
data !== null &&
(data.name === undefined || typeof data.name === 'string');
}
2. Environment Variable Doğrulaması
// Environment'i runtime'da degil baslangicta dogrula
interface Environment {
TABLE_NAME: string;
LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error';
NODE_ENV: 'development' | 'production';
}
function validateEnvironment(): Environment {
const env = process.env;
if (!env.TABLE_NAME) {
throw new Error('TABLE_NAME environment variable is required');
}
return {
TABLE_NAME: env.TABLE_NAME,
LOG_LEVEL: (env.LOG_LEVEL as any) || 'info',
NODE_ENV: (env.NODE_ENV as any) || 'development',
};
}
// Modul yuklenirken bir kez dogrula
const ENV = validateEnvironment();
3. Hata Yönetimi için Result Type’ları
// Temiz hata yonetimi icin Rust'tan ilham alinmis Result type
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
async function getItem(id: string): Promise<Result<Item, string>> {
try {
const result = await docClient.send(new GetCommand({
TableName: ENV.TABLE_NAME,
Key: { PK: 'ITEM', SK: `ITEM#${id}` },
}));
if (!result.Item) {
return { success: false, error: 'Item not found' };
}
return { success: true, data: transformDynamoItem(result.Item) };
} catch (error) {
return { success: false, error: error.message };
}
}
// Kullanim
const result = await getItem(id);
if (!result.success) {
return createResponse(404, { error: result.error });
}
// TypeScript result.data'nin Item oldugunu biliyor
const item = result.data;
Production Verisinden Performans Çıkarımları
Detaylı monitoring ile 18 ay production’da kaldıktan sonra:
Cold Start Analizi
- Ortalama cold start: 850ms
- P95 cold start: 1,200ms
- Bundle size etkisi: 10MB bundle = +400ms cold start
- Memory etkisi: 1024MB vs 512MB = -200ms cold start
Maliyet Dağılımı (Aylık)
- Lambda execution: $89/ay (8M invocation)
- API Gateway: $28/ay (8M request)
- DynamoDB: $67/ay (pay-per-request)
- CloudWatch logs: $12/ay
- Toplam: $196/ay (EC2 eşdeğeri için $800/ay ile karşılaştırıldığında)
Güvenilirlik Metrikleri
- Uptime: %99.97 (EC2’de %99.9’a karşı)
- Error rate: %0.02 (çoğunlukla client hataları)
- P95 response time: 180ms
Serverless Ne Zaman Kullanılmamalı
Serverless her zaman doğru araç değildir. Container’lar şu durumlar için daha iyi seçenek olmayı sürdürür:
- Uzun süren süreçler - Video encoding, büyük batch joblar
- Websocket ağırlıklı uygulamalar - Gerçek zamanlı oyun, chat uygulamaları
- Legacy uygulamalar - Karmaşık deployment gereksinimleri
- Stateful workload’lar - In-memory cache’ler, session’lar
- Cold start hassas - Sub-100ms yanıt gereksinimleri
Kırılmayan Deployment Pipeline’ı
// Zero-downtime deployment'lar icin CDK pipeline'i
export class ServerlessPipeline extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
const pipeline = new CodePipeline(this, 'Pipeline', {
synth: new ShellStep('Synth', {
input: CodePipelineSource.gitHub('yourorg/repo', 'main'),
commands: [
'npm ci',
'npm run build',
'npm run test',
'npx cdk synth',
],
}),
});
// Asamali rollout ile stage deployment'lari
const testStage = new ServerlessStage(this, 'Test', {
stageName: 'test',
});
const prodStage = new ServerlessStage(this, 'Prod', {
stageName: 'prod',
});
pipeline.addStage(testStage, {
post: [
new ShellStep('IntegrationTests', {
commands: [
'npm run test:integration',
],
envFromCfnOutput: {
API_URL: testStage.apiUrl,
},
}),
],
});
pipeline.addStage(prodStage, {
pre: [
new ManualApprovalStep('PromoteToProd'),
],
post: [
new ShellStep('SmokeTests', {
commands: [
'npm run test:smoke',
],
}),
],
});
}
}
Sonuç
TypeScript ile serverless, deployment kadansını ve operasyonel profili değiştirir. Haftalık deployment’lar günlük hale gelir; bellek-süre dengesi doğru ayarlandığında AWS maliyetleri önemli ölçüde düşer; altyapı katmanı ortadan kalktığı için uptime %99.97’ye ulaşır.
Temel kazanım, azaltılmış operasyonel yüktür: sunucu çökmelerinden kaynaklanan daha az incident, minimal kapasite planlaması ve OS yaması yok.
Serverless öğrenme eğrisi diktir, ancak üretkenlik kazanımları ölçülebilir. Basit bir CRUD API ile başlayın, ilk günden kapsamlı monitoring uygulayın ve platformun özellikleri tanıdık hale geldikçe kademeli olarak inşa edin.
Kaynaklar
- AWS Lambda nedir? - AWS Lambda - Temel kavramlar: yürütme modeli, fiyatlandırma ve desteklenen runtime’lar
- TypeScript ile Lambda fonksiyonu oluşturma - TypeScript’i transpile ederek Lambda’ya CDK veya zip arşivi ile dağıtma rehberi
- AWS Lambda fonksiyonları için en iyi uygulamalar - Handler başlatma, bağlantı yeniden kullanımı ve paket boyutu konusunda AWS tavsiyeleri
- Lambda fonksiyon ölçeklendirmeyi anlama - Lambda’nın eş zamanlılığı nasıl ölçeklendirdiği, ayrılmış eş zamanlılık ve burst limitleri
- DynamoDB ile tasarım için en iyi uygulamalar - Tek tablo tasarımı, partition key seçimi ve sıcak partition sorunlarından kaçınma
- AWS CDK v2 Geliştirici Rehberi - Üretim CDK stack örneklerinde kullanılan altyapı-kod çerçevesi
- Serverless Uygulamalar Lens - AWS Well-Architected - Serverless iş yükleri için maliyet optimizasyonu, güvenilirlik ve performansı kapsayan rehber
İlgili yazılar
AWS Lambda, API Gateway, DynamoDB ve Step Functions için hızlı geri bildirim ve production güvenilirliği sağlayan kapsamlı bir test stratejisi oluşturmayı öğrenin.
AWS CDK, DynamoDB ve Lambda ile production-grade link kısaltıcı kurulumu. Gerçek mimari kararlar, ilk kurulum ve büyük ölçekte URL kısaltıcıları inşa etmenin dersleri.
Yönlendirme motoru, analytics toplama ve API Gateway konfigürasyonu. Günlük milyonlarca yönlendirmeyi işlemenin gerçek performans optimizasyonları ve debugging stratejileri.
'Basit' bir API değişikliği bir kurumsal müşteri entegrasyonunu nasıl bozdu, dokümantasyon drift'i neden gerçek sorunlara yol açar ve Zod schema'larından otomatik OpenAPI spec'i üreten pratik bir sistem.
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.