İçeriğe atla

Tekrar Kullanılabilir AWS CDK Construct'ları: Factory Function, Builder ve Aspect'ler

Factory function'lar, higher-order function'lar ve composition ile AWS CDK'yı type-safe, tekrar kullanılabilir bir infrastructure toolkit'e dönüştürmek.

Ayhan Sipahi Ayhan Sipahi

AWS CDK, infrastructure’ı gerçek kod olarak ele almanı sağlar. Ortak pattern’ler olmadan ise takımlar duplicate konfigürasyonlarla, tutarsız ayarlarla ve type checker’ın yakalayabileceği deployment hatalarıyla baş başa kalır. Sorunun büyük kısmını iki hamle çözer: tekrarlayan her kaynağı bir factory function’ın arkasına koymak ve kimsenin unutmayı göze alamayacağı ayarları (RemovalPolicy, encryption, log retention) CDK Aspects ile enforce etmek. Builder’lar, composer’lar ve custom L3 construct’lar bu ikisinin uzantısıdır; sonradan eklenir.

Bedeli dolaylılık. Bir Lambda createApiLambda ile oluşturulduğunda, okuyucunun o function’ın gerçekte hangi ayarları aldığını görmek için factory’yi açması gerekir. Bu bedel, tekrarlayan kaynaklar ve her yerde geçerli olması gereken policy’ler için ödemeye değer; tek seferlik bir bucket için değmez.

İlgili yazılar: TypeScript’te creational pattern’ler ve builder pattern’ler genel şekilleri anlatıyor; buradaki bölümler bunları AWS CDK infrastructure’ına uyguluyor. Environment management ve migration context için Serverless’ten CDK’ya migration Bölüm 4’e göz at.

Configuration Drift Problemi#

Birden fazla microservice’de AWS CDK ile çalışırken tekrarlayan bir pattern ortaya çıkıyor: farklı developer’lar Lambda function’larını farklı şekillerde konfigüre ediyor. Bazıları log retention ayarlamayı unutuyor, diğerleri tutarsız timeout değerleri kullanıyor, memory size’lar ise benzer workload’lar arasında gerekçesiz değişiyor. Production veritabanları RemovalPolicy hiç ayarlanmadığı için siliniyor; development veritabanları da süresiz korunup sessizce maliyet biriktiriyor.

Temel sorun bilgi eksikliği değildi; enforcement eksikliğiydi. Her stack’e copy-paste edilen bir Lambda konfigürasyon bloğu, her stack’te ayrı ayrı düzeltilmek zorunda kalıyor. Gereksinim değiştiğinde bunu tutarlı şekilde güncellemek proje çapında bir refactoring işine dönüşüyor.

Merkezi NodejsFunction Konfigürasyon Factory#

Önemli bir fark yaratan en basit pattern, Lambda konfigürasyonları için bir factory function oluşturmaktı.

Pattern Olmadan:

// Her Lambda stack'te tekrarlanan kod
new NodejsFunction(this, 'UserHandler', {
  runtime: Runtime.NODEJS_20_X,
  handler: 'handler',
  entry: 'src/handlers/user.ts',
  timeout: Duration.seconds(30),
  memorySize: 1024,
  logRetention: RetentionDays.ONE_WEEK,
  tracing: Tracing.ACTIVE,
  environment: {
    NODE_OPTIONS: '--enable-source-maps',
    LOG_LEVEL: 'info'
  },
  bundling: {
    minify: true,
    sourceMap: true,
    externalModules: ['@aws-sdk/*'],
    mainFields: ['module', 'main']
  }
});

// Aynı config OrderHandler, ProductHandler vb. için duplicate edilmiş

Factory Pattern ile:

// lib/constructs/lambda-factory.ts
export interface LambdaConfig {
  entry: string;
  handler?: string;
  environment?: Record<string, string>;
  timeout?: Duration;
  memorySize?: number;
}

export function createApiLambda(
  scope: Construct,
  id: string,
  config: LambdaConfig
): NodejsFunction {
  return new NodejsFunction(scope, id, {
    runtime: Runtime.NODEJS_20_X,
    handler: config.handler ?? 'handler',
    entry: config.entry,
    timeout: config.timeout ?? Duration.seconds(30),
    memorySize: config.memorySize ?? 1024,
    logRetention: RetentionDays.ONE_WEEK,
    tracing: Tracing.ACTIVE,
    environment: {
      NODE_OPTIONS: '--enable-source-maps',
      LOG_LEVEL: process.env.STAGE === 'prod' ? 'warn' : 'debug',
      ...config.environment
    },
    bundling: {
      minify: true,
      sourceMap: true,
      externalModules: ['@aws-sdk/*'],
      mainFields: ['module', 'main']
    }
  });
}

// Kullanım - temiz ve tutarlı
const userHandler = createApiLambda(this, 'UserHandler', {
  entry: 'src/handlers/user.ts',
  environment: { TABLE_NAME: userTable.tableName }
});

Kazanç asıl olarak runtime yükseltmelerinde görünür. AWS yeni bir Node.js runtime’ı yayınlayıp eskisi deprecation’a yaklaştığında, değişiklik her function tanımı için ayrı bir düzenleme değil, factory’de tek bir satırdır; geçen hafta eklenmiş bir function’ı atlama ihtimali de kalmaz.

Higher-Order Function’lar ile RemovalPolicy Enforcement#

Bir dev stack’ten kopyalanan RemovalPolicy.DESTROY yüzünden production table’ının silinmesi, kurtarma maliyeti yüksek ve sık görülen bir hatadır. Environment’a özgü policy’ler, onları her stateful kaynağa otomatik uygulayan tek bir yerde durmalıdır.

Pattern Olmadan:

// Unutulması kolay, takım genelinde tutarsız
const userTable = new Table(this, 'UserTable', {
  partitionKey: { name: 'id', type: AttributeType.STRING },
  billingMode: BillingMode.PAY_PER_REQUEST,
  removalPolicy: RemovalPolicy.RETAIN // Manuel ayarlanmış, belki unutulmuş
});

const sessionTable = new Table(this, 'SessionTable', {
  partitionKey: { name: 'sessionId', type: AttributeType.STRING },
  billingMode: BillingMode.PAY_PER_REQUEST
  // RemovalPolicy unutulmuş - CloudFormation davranışına göre default
});

Higher-Order Function ile:

// lib/utils/removal-policy.ts
export function withRemovalPolicy<T extends Construct>(
  construct: T,
  environment: string
): T {
  const policy = environment === 'prod'
    ? RemovalPolicy.RETAIN
    : RemovalPolicy.DESTROY;

  if (construct instanceof Table) {
    construct.applyRemovalPolicy(policy);
  } else if (construct instanceof Bucket) {
    construct.applyRemovalPolicy(policy);
  } else if (construct instanceof FileSystem) {
    construct.applyRemovalPolicy(policy);
  }

  return construct;
}

// Kullanım - policy environment'a göre otomatik
const userTable = withRemovalPolicy(
  new Table(this, 'UserTable', {
    partitionKey: { name: 'id', type: AttributeType.STRING },
    billingMode: BillingMode.PAY_PER_REQUEST
  }),
  this.stage // 'dev' veya 'prod'
);

Daha İyi - CDK Aspects Kullanarak:

// TÜM stateful kaynaklar için otomatik uygula
export class RemovalPolicyAspect implements IAspect {
  constructor(private readonly environment: string) {}

  visit(node: IConstruct): void {
    const policy = this.environment === 'prod'
      ? RemovalPolicy.RETAIN
      : RemovalPolicy.DESTROY;

    if (node instanceof CfnTable) {
      node.applyRemovalPolicy(policy);
    } else if (node instanceof CfnBucket) {
      node.applyRemovalPolicy(policy);
    } else if (node instanceof CfnDBCluster) {
      node.applyRemovalPolicy(policy);
    }
  }
}

// Stack genelinde uygula
Aspects.of(this).add(new RemovalPolicyAspect(this.stage));

Bir Aspect, synthesis sırasında construct ağacındaki her node’u ziyaret eder; böylece policy, stack dosyalarında adı hiç geçmeyen kaynaklara da ulaşır. Wrapper ile Aspect arasındaki fark tam olarak budur: wrapper yalnızca birinin sarmayı hatırladığı table’ları korur.

Birleştirilebilir Konfigürasyon Builder’ları#

Lambda function’ları genellikle farklı feature kombinasyonlarına ihtiyaç duyar: bazılarının VPC erişimine, bazılarının layer’lara, bazılarının DLQ’ya, bazılarının ise hepsine ihtiyacı vardır. Bu kombinasyonları temiz şekilde konfigüre etmek composition yaklaşımı gerektirir.

// lib/constructs/lambda-composers.ts
export type LambdaComposer = (fn: NodejsFunction) => void;

export const withVpc = (vpc: IVpc, subnets: SubnetSelection): LambdaComposer =>
  (fn) => {
    // VPC konfigürasyonu ekle
    // Not: gerçek implementasyon VPC props ile yeniden oluşturmayı gerektirir
  };

export const withDLQ = (queue?: IQueue): LambdaComposer =>
  (fn) => {
    const dlq = queue ?? new Queue(fn, 'DLQ', {
      retentionPeriod: Duration.days(14)
    });
    fn.addEnvironment('DLQ_URL', dlq.queueUrl);
  };

export const withLayer = (layer: ILayerVersion): LambdaComposer =>
  (fn) => {
    fn.addLayers(layer);
  };

export const withAlarm = (
  errorThreshold: number = 10
): LambdaComposer =>
  (fn) => {
    new Alarm(fn, 'ErrorAlarm', {
      metric: fn.metricErrors(),
      threshold: errorThreshold,
      evaluationPeriods: 2
    });
  };

// Birden fazla davranışı compose et
export function composeLambda(
  fn: NodejsFunction,
  ...composers: LambdaComposer[]
): NodejsFunction {
  composers.forEach(composer => composer(fn));
  return fn;
}

// Kullanım - temiz composition
const apiHandler = composeLambda(
  createApiLambda(this, 'ApiHandler', {
    entry: 'src/handlers/api.ts'
  }),
  withDLQ(),
  withLayer(sharedLayer),
  withAlarm(5)
);

Bu pattern, karmaşık Lambda konfigürasyonlarını basit, tekrar kullanılabilir parçalardan oluşturmana olanak tanır. Her composer function bir cross-cutting concern’i ele alır ve gerektiği gibi combine edilebilir.

Type-Safe Environment Konfigürasyonu#

Environment’a özgü ayarların (VPC ID’ler, subnet ID’ler, domain name’ler) kod boyunca dağılmış olması, environment’lar arasında neyin değiştiğini anlamayı zorlaştırır. Strongly-typed bir konfigürasyon pattern’i bunu çözer.

// config/environment.ts
import { z } from 'zod';

const EnvironmentSchema = z.object({
  stage: z.enum(['dev', 'staging', 'prod']),
  account: z.string().regex(/^\d{12}$/),
  region: z.string(),
  vpc: z.object({
    id: z.string(),
    privateSubnetIds: z.array(z.string()).min(2),
    publicSubnetIds: z.array(z.string()).min(2)
  }),
  domain: z.string(),
  logRetention: z.number().int().positive(),
  lambdaDefaults: z.object({
    timeout: z.number().int().min(3).max(900),
    memorySize: z.number().int().min(128).max(10240)
  }),
  monitoring: z.object({
    enableXRay: z.boolean(),
    enableDetailedMetrics: z.boolean()
  })
});

export type EnvironmentConfig = z.infer<typeof EnvironmentSchema>;

// config/dev.ts
export const devConfig: EnvironmentConfig = {
  stage: 'dev',
  account: '123456789012',
  region: 'us-east-1',
  vpc: {
    id: 'vpc-dev123',
    privateSubnetIds: ['subnet-dev1', 'subnet-dev2'],
    publicSubnetIds: ['subnet-pub1', 'subnet-pub2']
  },
  domain: 'dev.example.com',
  logRetention: 7, // günler
  lambdaDefaults: {
    timeout: 30,
    memorySize: 512
  },
  monitoring: {
    enableXRay: false,
    enableDetailedMetrics: false
  }
};

// config/prod.ts
export const prodConfig: EnvironmentConfig = {
  stage: 'prod',
  account: '210987654321',
  region: 'us-east-1',
  vpc: {
    id: 'vpc-prod456',
    privateSubnetIds: ['subnet-prod1', 'subnet-prod2', 'subnet-prod3'],
    publicSubnetIds: ['subnet-pub1', 'subnet-pub2', 'subnet-pub3']
  },
  domain: 'api.example.com',
  logRetention: 90,
  lambdaDefaults: {
    timeout: 60,
    memorySize: 1024
  },
  monitoring: {
    enableXRay: true,
    enableDetailedMetrics: true
  }
};

// config/index.ts
export function getConfig(stage: string): EnvironmentConfig {
  const configs = { dev: devConfig, staging: stagingConfig, prod: prodConfig };
  const config = configs[stage as keyof typeof configs];

  if (!config) {
    throw new Error(`Bilinmeyen stage: ${stage}`);
  }

  return EnvironmentSchema.parse(config); // Runtime validation
}

// Stack'te kullanım
export class ApiStack extends Stack {
  constructor(scope: Construct, id: string, config: EnvironmentConfig) {
    super(scope, id, {
      env: {
        account: config.account,
        region: config.region
      }
    });

    const vpc = Vpc.fromLookup(this, 'Vpc', { vpcId: config.vpc.id });

    const handler = createApiLambda(this, 'Handler', {
      entry: 'src/handlers/api.ts',
      timeout: Duration.seconds(config.lambdaDefaults.timeout),
      memorySize: config.lambdaDefaults.memorySize
    });

    if (config.monitoring.enableXRay) {
      handler.addToRolePolicy(xrayPolicy);
    }
  }
}

Zod validation, konfigürasyon hatalarını runtime’da (synth sırasında) yakalar ve geçersiz deployment’ları önler. TypeScript, compile-time type safety ve mükemmel IDE autocomplete desteği sağlar.

Makul Varsayılanlarla Custom L3 Construct’lar#

Her API endpoint için Lambda + API Gateway + DynamoDB table + CloudWatch alarm’ları gerektiğinde çok fazla tekrarlayan kod ortaya çıkar. Custom L3 construct’lar bu pattern’leri kapsüller.

// lib/constructs/api-endpoint.ts
export interface ApiEndpointProps {
  readonly handlerEntry: string;
  readonly tableName: string;
  readonly partitionKey: Attribute;
  readonly sortKey?: Attribute;
  readonly environment?: Record<string, string>;
  readonly timeout?: Duration;
  readonly memorySize?: number;
}

export class ApiEndpoint extends Construct {
  public readonly handler: NodejsFunction;
  public readonly table: Table;
  public readonly api: RestApi;

  constructor(scope: Construct, id: string, props: ApiEndpointProps) {
    super(scope, id);

    // Best practice'lerle DynamoDB table oluştur
    this.table = new Table(this, 'Table', {
      tableName: props.tableName,
      partitionKey: props.partitionKey,
      sortKey: props.sortKey,
      billingMode: BillingMode.PAY_PER_REQUEST,
      encryption: TableEncryption.AWS_MANAGED,
      pointInTimeRecovery: true,
      removalPolicy: RemovalPolicy.RETAIN,
      stream: StreamViewType.NEW_AND_OLD_IMAGES
    });

    // Standart ayarlarla Lambda oluştur
    this.handler = createApiLambda(this, 'Handler', {
      entry: props.handlerEntry,
      timeout: props.timeout,
      memorySize: props.memorySize,
      environment: {
        TABLE_NAME: this.table.tableName,
        ...props.environment
      }
    });

    // Permission'ları ver
    this.table.grantReadWriteData(this.handler);

    // API Gateway oluştur
    this.api = new RestApi(this, 'Api', {
      restApiName: `${id}-api`,
      deployOptions: {
        stageName: 'v1',
        tracingEnabled: true,
        loggingLevel: MethodLoggingLevel.INFO,
        metricsEnabled: true
      }
    });

    const integration = new LambdaIntegration(this.handler);
    this.api.root.addMethod('ANY', integration);

    // Monitoring ekle
    new Alarm(this, 'ErrorAlarm', {
      metric: this.handler.metricErrors(),
      threshold: 5,
      evaluationPeriods: 2,
      alarmDescription: `${id} için hatalar`
    });

    new Alarm(this, 'ThrottleAlarm', {
      metric: this.handler.metricThrottles(),
      threshold: 1,
      evaluationPeriods: 1,
      alarmDescription: `${id} için throttle'lar`
    });
  }

  // Ek route'lar için helper method
  public addRoute(
    path: string,
    method: string,
    handler: IFunction
  ): void {
    const resource = this.api.root.resourceForPath(path);
    resource.addMethod(method, new LambdaIntegration(handler));
  }
}

// Kullanım - table, handler, API ve iki alarm tek construct'ta
const userEndpoint = new ApiEndpoint(this, 'UserEndpoint', {
  handlerEntry: 'src/handlers/user.ts',
  tableName: 'users',
  partitionKey: { name: 'userId', type: AttributeType.STRING }
});

Bu custom construct, best practice’leri kapsüller: encryption at rest, production için point-in-time recovery, uygun IAM permission’ları, API Gateway logging ve CloudWatch alarm’ları. Yeni takım üyeleri her detayı anlamadan kullanabilir.

CDK Aspects ile Policy Enforcement#

Security gereksinimleri (encryption at rest, encryption in transit, public S3 bucket olmaması, her şey için CloudWatch log’ları) otomatik enforcement gerektirir.

// lib/aspects/security-compliance.ts
export class S3EncryptionAspect implements IAspect {
  visit(node: IConstruct): void {
    if (node instanceof CfnBucket) {
      if (!node.bucketEncryption) {
        Annotations.of(node).addError(
          'S3 bucket\'larda encryption aktif olmalı'
        );
      }
    }
  }
}

export class PublicAccessBlockAspect implements IAspect {
  visit(node: IConstruct): void {
    if (node instanceof CfnBucket) {
      if (!node.publicAccessBlockConfiguration) {
        node.publicAccessBlockConfiguration = {
          blockPublicAcls: true,
          blockPublicPolicy: true,
          ignorePublicAcls: true,
          restrictPublicBuckets: true
        };
      }
    }
  }
}

export class LambdaLogRetentionAspect implements IAspect {
  constructor(private readonly retentionDays: RetentionDays) {}

  visit(node: IConstruct): void {
    if (node instanceof NodejsFunction || node instanceof Function) {
      const cfnFunction = node.node.defaultChild as CfnFunction;

      // Retention policy ile log group olduğundan emin ol
      new LogGroup(node, 'LogGroup', {
        logGroupName: `/aws/lambda/${cfnFunction.ref}`,
        retention: this.retentionDays,
        removalPolicy: RemovalPolicy.DESTROY
      });
    }
  }
}

// Stack genelinde uygula
export class SecureStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Bu stack'teki her şey için security policy'leri enforce et
    Aspects.of(this).add(new S3EncryptionAspect());
    Aspects.of(this).add(new PublicAccessBlockAspect());
    Aspects.of(this).add(new LambdaLogRetentionAspect(RetentionDays.ONE_WEEK));
  }
}

Bu üç sınıf bilerek farklı davranıyor. S3EncryptionAspect synth’i error annotation ile düşürür, PublicAccessBlockAspect kaynağı sessizce düzeltir, LambdaLogRetentionAspect ise eksik olan bir kaynağı ekler. Bir takımın haklı olarak override etmek isteyebileceği her şey için annotate etmek daha güvenli seçimdir; çünkü sessiz bir mutasyon, kararı stack’i sonradan okuyandan gizler.

Yaygın Hatalar ve Çözümleri#

Aşırı Abstraction#

Basit olanlar dahil her kaynak için factory function oluşturmak, faydası olmayan abstraction overhead’i yaratır. Pattern’leri, tekrarlanan konfigürasyonlara veya karmaşık validation gereksinimlerine sahip kaynaklara seçici olarak uygula. Tek bir S3 bucket factory’ye ihtiyaç duymaz.

Ne zaman abstract etmeli:

  • Kaynak benzer config ile 3+ kez görünüyor
  • Karmaşık validation logic gerekli
  • Environment’a özgü varyasyonlar gerekli
  • Security/compliance policy’leri enforce edilmeli

Implicit Dependency’ler#

Belirli kaynakların (VPC, security group’lar) var olduğunu dependency’leri explicit yapmadan varsayan factory function’lar kırılgan kod yaratır. Dependency’leri function parametreleri aracılığıyla explicit yap.

// Kötü - 'vpc' nereden geliyor?
function createLambda(entry: string): NodejsFunction {
  return new NodejsFunction(this, 'Fn', {
    entry,
    vpc, // Implicit dependency
  });
}

// İyi - explicit dependency
function createLambda(
  scope: Construct,
  id: string,
  entry: string,
  vpc: IVpc
): NodejsFunction {
  return new NodejsFunction(scope, id, { entry, vpc });
}

Wrapper’larda Type Safety Kaybı#

any type’ları veya aşırı permissive generic’ler kullanmak TypeScript’in type checking’ini yok eder. Factory layer’ları boyunca strict type’ları koru.

// Kötü - type safety kayboldu
function createResource(props: any): any {
  // ...
}

// İyi - type safety korundu
function createResource<T extends Construct, P>(
  constructClass: new (scope: Construct, id: string, props: P) => T,
  scope: Construct,
  id: string,
  props: P
): T {
  return new constructClass(scope, id, props);
}

Environment’lar Arasında Configuration Drift#

Dev vs prod’da farklı konfigürasyon pattern’leri kullanmak “dev’de çalışıyor” production failure’larına neden olur. Tüm environment’lar için aynı code path’i kullan, sadece konfigürasyon değerlerinde farklılık olsun.

// Aynı factory, farklı config
const config = getConfig(stage); // Type-safe config

const lambda = createApiLambda(this, 'Handler', {
  entry: 'src/handler.ts',
  timeout: Duration.seconds(config.lambdaDefaults.timeout),
  memorySize: config.lambdaDefaults.memorySize
});

Infrastructure Code’u Test Etme#

Infrastructure code, application code gibi test edilmeli. Resource property’lerini verify etmek için CDK assertions library’yi kullan.

import { Template } from 'aws-cdk-lib/assertions';

test('Lambda factory doğru ayarlarla function oluşturur', () => {
  const stack = new Stack();

  const fn = createApiLambda(stack, 'TestFn', {
    entry: 'src/test.ts'
  });

  const template = Template.fromStack(stack);

  template.hasResourceProperties('AWS::Lambda::Function', {
    Runtime: 'nodejs20.x',
    Timeout: 30,
    MemorySize: 1024,
    TracingConfig: { Mode: 'Active' }
  });
});

Test yapmak konfigürasyon hatalarını yakalar ve factory function’ların beklenen CloudFormation resource’larını ürettiğini validate eder.

Factory’ler Ne Zaman Değer Katar#

Bir kaynak tipi üç ya da daha fazla yerde göründüğünde veya bir ayarın kimse hatırlamasa bile her yerde geçerli olması gerektiğinde factory’ye geç. RemovalPolicy, encryption ve log retention bu ikinci gruba girer; bu yüzden factory parametresi yerine Aspect’e konur, çünkü Aspect bir meslektaşının önümüzdeki ay yazacağı stack’i de kapsar. Builder’ları, composer’ları ve custom L3 construct’ları ise ancak gizledikleri kombinasyonlar kod tabanında gerçekten ortaya çıktıktan sonra üstüne ekle.

Stack baştan sona okunacak kadar küçükse, kaynak gerçekten tek seferlikse ya da takım standardın ne olacağı konusunda henüz anlaşmadıysa bu varsayılanı bir kenara bırak. Pattern ortaya çıkmadan yazılan bir factory yalnızca bir tahmini kodlar; paylaşılan infrastructure kodundaki yanlış bir tahmini geri almak, yerine geçtiği duplikasyondan daha zordur. Yukarıdaki pattern’ler arasında ilk benimsenecek olan Aspect’tir, çünkü hatanın parayla ödendiği yer enforcement’tır; sonrasında factory’yi dürüst tutan şey de unit test’teki Template.fromStack’tir.

Kaynaklar#

İlgili yazılar

Stateful Kaynaklar Ayrı Bir CDK Stack'inde mi Durmalı?

CDK stack düzeni için yaşam döngüsü testi: bir kaynağın ömrü tek bir dağıtımdan uzunsa kendi uzun ömürlü stack'ine koyun, ona bilinen bir adla erişin.

aws-cdk · infrastructure-as-code · typescript +3

AWS CDK Proje Yapısı: Service-Based ve Domain-Based Organizasyon

AWS CDK projelerinde service-based, domain-based, feature-based ve layer-based organizasyon patternlerini karar çerçeveleri ve örneklerle ne zaman seçeceğini öğren.

aws-cdk · typescript · infrastructure-as-code +3

AWS Lambda'da Effect: SST ve DynamoDB ile URL Kısaltıcı Oluşturma

AWS Lambda ve DynamoDB üzerinde Effect ve SST v4 ile küçük, eksiksiz bir URL kısaltıcı; sınırda şema, layer'lar ve tagged error eşlemesini gösterir.

functional-programming · lambda · typescript +2

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, IAM ve Marketplace ön koşulları.

aws-bedrock · ai-agents · aws-cdk +3

CloudFormation'un 500 Kaynak Sınırını Aşmak: Büyük Ölçekli Altyapı için Pratik Stratejiler

CloudFormation'un 500 kaynak sınırını nested stack, cross-stack referans, SSM Parameter Store ve microstack mimarisiyle aşmayı TypeScript CDK örnekleriyle öğren.

aws-cdk · infrastructure-as-code · aws +2