İçeriğe atla

2025-09-04

Serverless Framework'ten AWS CDK'ya Geçiş: Bölüm 2 - CDK Environment'ınızı Kurma

Serverless uygulamalar için CDK projesini nasıl yapılandıracağınızı, Lambda development için TypeScript'i nasıl configure edeceğinizi ve Serverless Framework'ten migration'u kolaylaştıran pattern'leri nasıl kuracağınızı öğrenin.

CDK migration’ımızın 1. haftası. Karar verilmişti, bütçe onaylanmıştı ve 12 kişilik ekibim beklenti içinde bana bakıyordu. “Peki, nereden başlıyoruz?”

CDK’yı kişisel projelerde kullanmıştım, ama onu 4 environment’ta 12 developer ile 47 Lambda fonksiyonunu handle edecek şekilde scale etmek? Bu farklıydı. Sadece benim için değil, tüm ekip için çalışacak yapı, kurallar ve pattern’lere ihtiyacımız vardı.

Bu, 12 mühendisinin birbirlerinin ayağına basmadan paralel çalışmasını sağlayan, Serverless Framework’teki tanıdık pattern’leri koruyan ve 2.8M$ ARR platformumuzun temeli haline gelen bir CDK proje yapısını nasıl tasarladığımızın hikayesi.

Seri Navigasyonu:

Gerçekten Scale Olan Proje Yapısı

İlk denememiz felaketti. Naif bir şekilde basit CDK tutorial yapısını kopyaladım ve bir hafta içinde merge conflict’leri, belirsiz ownership ve “Bu dosya nereye gider?” diye soran kafası karışmış mühendisler vardı.

İşte kaostan düzene evrim:

# Serverless Framework Yapısı
my-service/
├── serverless.yml
├── package.json
├── src/
  └── handlers/
  ├── users.js
  └── products.js
├── resources/
  └── dynamodb-tables.yml
└── config/
    ├── dev.yml
    └── prod.yml

# CDK Yapısı (3 başarısız denemeden sonra)
my-service/
├── cdk.json  # CDK app konfigürasyonu
├── package.json
├── bin/
  └── my-service.ts  # Tek entry point (zor yoldan öğrendik)
├── lib/
  ├── stacks/  # Domain'e göre stack tanımları
  ├── api-stack.ts  # API Gateway + Lambda fonksiyonları
  ├── data-stack.ts  # DynamoDB tabloları (stateful)
  └── auth-stack.ts  # Cognito + auth logiği
  ├── constructs/  # Reusable pattern'ler (sihirli formülümüz)
  ├── production-lambda.ts  # 376 fonksiyon bunu kullanıyor
  ├── api-with-auth.ts  # Her API bu pattern'i takip ediyor
  └── monitored-table.ts  # Alarm'lı DynamoDB
  └── config/  # Environment-spesifik config'ler
  ├── development.ts
  ├── staging.ts
  └── production.ts
├── src/
  └── handlers/  # Lambda kodu (tanıdık lokasyon)
  ├── users/  # Domain'e göre gruplandırılmış
  ├── create.ts
  ├── update.ts
  └── list.ts
  └── products/
  ├── catalog.ts
  └── inventory.ts
└── test/
    ├── unit/  # Handler unit testleri
    ├── integration/  # API integration testleri
    └── infrastructure/  # CDK stack testleri

Anahtar anlayış: Domain-driven organizasyon, 12 mühendis paralel çalışırken merge conflict’leri önlüyor.

CDK Projenizi Initialize Etme

Önce prerequisite’lerin olduğundan emin olun:

# AWS CDK CLI'yi global olarak yükle
npm install -g aws-cdk@2

# Kurulumu doğrula
cdk --version  # 2.x.x göstermeli

# AWS credential'larını configure et (henüz yapılmadıysa)
aws configure

Şimdi projenizi oluşturun:

# Proje dizini oluştur
mkdir my-serverless-api && cd my-serverless-api

# TypeScript ile CDK initialize et
cdk init app --language typescript

# Lambda-spesifik dependency'leri yükle
npm install @types/aws-lambda

# Development araçlarını yükle
npm install --save-dev esbuild @types/node ts-node

Lambda Development için TypeScript Konfigürasyonu

CDK temel bir tsconfig.json oluşturur. Serverless development için optimize edelim:

{
  "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"
  ]
}

Environment Configuration Yönetimi

Serverless Framework environment-spesifik konfigürasyon için YAML dosyaları kullanır. TypeScript tabanlı bir eşdeğer oluşturalım:

// 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: '[email protected]',
    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(`Unknown stage: ${stage}`);
  }
}

İlk Construct’ınızı Oluşturma

Construct’lar CDK’nın yapı taşlarıdır. Lambda fonksiyonları için reusable bir pattern oluşturalım:

// 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, // En yeni özellikler için NODEJS_22_X'i düşünün
      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'ü hariç tut (Lambda runtime'da sağlanıyor)
        externalModules: [
          '@aws-sdk/*',
        ],
      },
      reservedConcurrentExecutions: config.lambda.reservedConcurrentExecutions,
    });
  }
}

İlk Stack’inizi Kurma

Şimdi construct’ımızı kullanan bir stack oluşturalım:

// 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 oluştur
    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 fonksiyonları oluştur
    const createUserFn = new ServerlessFunction(this, 'CreateUserFunction', {
      entry: 'src/handlers/users.ts',
      handler: 'create',
      config,
      environment: {
        // Environment variable'lar Bölüm 4'te eklenecek
      },
    });

    // Route'ları kur
    const users = this.api.root.addResource('users');
    users.addMethod('POST', new LambdaIntegration(createUserFn));
  }
}

CDK App Entry Point

CDK app entry point’ini konfigürasyon sistemimizi kullanacak şekilde güncelleyin:

// 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();

// Context veya environment'tan stage al
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',
  },
});

İlk Lambda Handler’ınız

TypeScript kullanarak bir Lambda handler oluşturun:

// 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 logiği burada (Bölüm 3'te genişletilecek)

    return {
      statusCode: 201,
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: 'User created successfully',
        stage: process.env.STAGE,
      }),
    };
  } catch (error) {
    console.error('Error:', error);

    return {
      statusCode: 500,
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        error: 'Internal server error',
      }),
    };
  }
};

Deployment Komutları

Bu script’leri package.json’nıza ekleyin:

{
  "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"
  }
}

İlk Deployment

AWS environment’ınızı bootstrap edin (CDK deployment’ları için AWS hesabınızı gerekli S3 bucket’ları ve IAM roller oluşturarak hazırlayan tek seferlik kurulum):

npm run bootstrap

Development’a deploy edin:

npm run deploy:dev

CDK hangi kaynakları oluşturmayı planladığını gösterecek. İnceleyin ve onaylayın.

Local Development Kurulumu

Serverless Framework’ün serverless-offline’ının aksine, CDK built-in local API Gateway emülasyonu sağlamıyor. Local development için birkaç seçeneğiniz var:

  1. SAM CLI Entegrasyonu (Önerilen):
# SAM CLI yükle
brew install aws-sam-cli  # macOS
# veya takip edin: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html

# CloudFormation template oluştur
cdk synth --no-staging > template.yaml

# Local API başlat
sam local start-api -t template.yaml
  1. Doğrudan Handler Testing:
// test/handlers/users.test.ts
import { create } from '../../src/handlers/users';
import { APIGatewayProxyEventV2 } from 'aws-lambda';

describe('Users Handler', () => {
  it('should create a user', 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');
  });
});

Hatırlanması Gereken Temel Farklar

AspektServerless FrameworkCDK
KonfigürasyonYAML dosyalarıTypeScript kodu
Environment Variable’lar${self:provider.stage}Config objeleri
Local Developmentserverless-offlineSAM CLI veya testing
Deploymentserverless deploycdk deploy
Kaynak Reference’ları!Ref veya ${cf:stackName.output}Doğrudan object reference’ları

Sırada Ne Var

Artık Serverless Framework geleneklerini yansıtan ama CDK’nın type safety ve composability’sini benimseyen sağlam bir CDK temeline sahipsiniz. Lambda fonksiyonlarınız tanıdık lokasyonlarda yaşıyor, ama altyapınız artık kod - gerçek, test edilebilir TypeScript kodu.

İlgili okuma: Farklı CDK organizasyon pattern’lerinin (service-based vs domain-based vs feature-based) kapsamlı karşılaştırması için, AWS CDK Kod Organizasyonu: Service-Based vs Domain-Based Architecture Pattern’leri’ne göz at.

3. Bölüm’te, şunları içeren Lambda fonksiyonları ve API Gateway konfigürasyonlarını migrate edeceğiz:

  • Request/response transformasyon’ları
  • API Gateway modelleri ve validator’ları
  • Lambda layer’ları ve dependency’leri
  • Error handling pattern’leri
  • API versioning stratejileri

Temel atıldı. Serverless API’nızı inşa edelim.

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.

İlerleme 2 / 6 yazı

İlgili yazılar

AWS Secrets Manager & Parameter Store: Güvenlik Best Practices

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.

awssecrets-managerparameter-store+8
AWS CDK Link Kısaltıcı Bölüm 1: Proje Kurulumu & Temel Altyapı

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.

aws-cdklambdadynamodb+6
AWS Bedrock ve CDK ile RAG Agent Kurmak

AWS Bedrock + Knowledge Bases + OpenSearch Serverless üstüne CDK ile TypeScript kullanarak RAG agent kurmak — mimari, IAM bağlantısı, otomatik ingestion ve chat UI.

aws-bedrockaws-cdkrag+3
AWS Bedrock AgentCore'u CDK ile deploy etmek: hızlı başlangıç

AgentCore Runtime üzerinde minimal bir Strands agent'ı CDK ile deploy etme rehberi — parametrize stack, arm64 build, deploy ve invoke akışı, ve ilk çağrıdan önce gereken IAM ve Marketplace ön koşulları.

aws-bedrockai-agentsaws-cdk+3
React Native Expo ile Sentry Entegrasyonu: Pratik Hızlı Rehber

React Native Expo uygulamasına Sentry hata izleme entegrasyonu için adım adım rehber. SDK başlatma, Expo Router enstrümantasyonu, session replay, EAS Build ve EAS Update için source map yükleme ve sık karşılaşılan sorunları kapsar.

react-nativeexpomonitoring+2