İçeriğe atla

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:

  1. Tepe yük için fazla provision (pahalı)
  2. 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:

  1. Uzun süren süreçler - Video encoding, büyük batch joblar
  2. Websocket ağırlıklı uygulamalar - Gerçek zamanlı oyun, chat uygulamaları
  3. Legacy uygulamalar - Karmaşık deployment gereksinimleri
  4. Stateful workload’lar - In-memory cache’ler, session’lar
  5. 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

İlgili yazılar