Skip to content
~/sph.sh

Type-Safe Lambda Middleware: Middy, Zod ve Builder Pattern ile Enterprise Uygulamalar

Middy builder pattern, Zod validation, feature flags ve secrets management kullanarak enterprise serverless uygulamaları için sürdürülebilir, type-safe Lambda middleware nasıl inşa edilir öğren.

Özet

Enterprise serverless uygulamaları temel middleware pattern'larından daha fazlasına ihtiyaç duyar. Bu yazıda Middy'yi temel alarak type-safe, sürdürülebilir Lambda middleware sistemleri inşa etmeyi keşfedeceğiz: enforced composition için builder pattern, mükemmel hata mesajları ile runtime validation için Zod, dinamik davranış kontrolü için feature flags ve doğru secrets management. Lambda ile ölçekli çalışmak bana compile-time type safety ve tutarlı middleware sıralamasının production'daki sorunları runtime validation'dan çok daha etkili önlediğini öğretti.

Standard Middleware Pattern'larındaki Problem

Lambda fonksiyonları yazarken, middleware hızlıca codebase genelinde tutarsız hale geliyor. Farklı developer'lar chain'leri farklı şekilde yapılandırıyor, validation hataları anlaşılmaz mesajlar veriyor ve configuration hataları sadece runtime'da ortaya çıkıyor.

Eğer Middy'ye yeniysen, temel konseptler ve pattern'lar için Middy ile AWS Lambda middleware yazımıza göz at.

Lambda codebase'lerinde genellikle şunu görüyorum:

typescript
// Hata yapmak çok kolay - compile-time checking yokexport const handler = middy(businessLogic)  .use(httpErrorHandler()) // Bu ilk mi son mu olmalı?  .use(validator({ eventSchema })) // Schema'nın event type'a uyduğunu doğrulama yok  .use(httpJsonBodyParser())  .use(httpCors())  // Authentication middleware'i unutulmuş!

Yaygın Sorunlar:

  • Middleware sıralamasının zorlanamaması (error handler yanlış pozisyonda)
  • Validation ile handler arasında type safety kopuyor (schema handler type'ları ile eşleşmiyor)
  • Fonksiyonlar arası tutarsız pattern'lar (bazılarında auth var, bazılarında yok)
  • Anlaşılmaz JSON Schema validation hataları
  • Feature flags ve secrets için tekrarlanan kod

Teknik Gereksinimler

Bu zorlukları ele almak için enterprise middleware sisteminin ihtiyaç duyduğu özellikler:

  1. Compile-time type safety: Configuration hatalarını deployment öncesi yakala
  2. Enforced middleware ordering: Tüm fonksiyonlarda tutarlı execution
  3. Daha iyi validation hataları: Schema validation'dan net, actionable mesajlar
  4. Feature flag integration: Kod deployment olmadan feature toggle
  5. Secrets management: Cache'lenmiş, rotation-aware secret erişimi
  6. Discoverable API: Autocomplete ve type hints ile developer guidance
  7. Testability: Middleware chain'lerini mock etmek ve test etmek kolay olmalı

Runtime Önerisi: Lambda fonksiyonları için Node.js 22.x kullan. Node.js 16 zaten deprecated, Node.js 18 tam deprecation'a 9 Mart 2026'da ulaştı ve Node.js 20 end-of-life'a 30 Nisan 2026'da ulaşacak. Serverless uygulamalarda TypeScript pattern'ları ve best practice'ler için TypeScript ile AWS Serverless yazımıza göz at.

Implementation: Type-Safe Builder Pattern

Builder pattern, middleware composition hakkında compile-time guarantee'ler sağlar. Her builder metodu, zenginleştirilmiş context ile yeni bir type döner, TypeScript'in handler'ınızda tam olarak neyin mevcut olduğunu bilmesini sağlar.

Core Builder Implementation

typescript
interface MiddlewareConfig {  enableAuth: boolean  enableCors: boolean  validationSchema?: z.ZodSchema  featureFlags?: string[]  secrets?: string[]}
class LambdaMiddlewareBuilder<TEvent, TContext = {}> {  private config: Partial<MiddlewareConfig> = {}
  withAuthentication(): LambdaMiddlewareBuilder<TEvent, TContext & { userId: string }> {    this.config.enableAuth = true    return this as any  }
  withValidation<TSchema extends z.ZodSchema>(    schema: TSchema  ): LambdaMiddlewareBuilder<z.infer<TSchema>, TContext> {    this.config.validationSchema = schema    return this as any  }
  withFeatureFlags(    flags: string[]  ): LambdaMiddlewareBuilder<TEvent, TContext & { features: Record<string, boolean> }> {    this.config.featureFlags = flags    return this as any  }
  withSecrets(    secrets: string[]  ): LambdaMiddlewareBuilder<TEvent, TContext & { secrets: Record<string, string> }> {    this.config.secrets = secrets    return this as any  }
  // Not: Bu implementation basitlik için `as any` kullanıyor. Production implementation'lar  // tam type safety'yi korumak için mapped types veya conditional types gibi daha sofistike  // TypeScript teknikleri kullanabilir.
  build(handler: (event: TEvent, context: TContext) => Promise<any>) {    const middlewareChain = middy(handler)
    // Tutarlı sıralamayı zorla    if (this.config.enableCors) {      middlewareChain.use(httpCors())    }
    middlewareChain.use(httpJsonBodyParser())
    if (this.config.validationSchema) {      middlewareChain.use(zodValidationMiddleware(this.config.validationSchema))    }
    if (this.config.enableAuth) {      middlewareChain.use(authenticationMiddleware())    }
    if (this.config.featureFlags) {      middlewareChain.use(featureFlagsMiddleware(this.config.featureFlags))    }
    if (this.config.secrets) {      middlewareChain.use(secretsMiddleware(this.config.secrets))    }
    middlewareChain.use(httpErrorHandler())
    return middlewareChain  }}

Full Type Safety ile Kullanım

typescript
const requestSchema = z.object({  email: z.string().email(),  password: z.string().min(8),  tenantId: z.string().uuid()})
export const handler = new LambdaMiddlewareBuilder()  .withAuthentication()  .withValidation(requestSchema)  .withFeatureFlags(['newLoginFlow', 'mfaEnabled'])  .withSecrets(['DATABASE_URL', 'JWT_SECRET'])  .build(async (event, context) => {    // TypeScript biliyor:    // - event requestSchema ile eşleşiyor (email, password, tenantId)    // - context'te userId var (auth'dan)    // - context'te features object var    // - context'te secrets object var
    if (context.features.mfaEnabled) {      // MFA flow'u handle et    }
    const dbUrl = context.secrets.DATABASE_URL    // Business logic tam type safety ile  })

Temel Faydalar:

  • Context type'larının compile-time checking'i
  • Zorlanmış middleware sıralaması
  • Autocomplete ile discoverable API
  • Middleware configuration için tek gerçek kaynak

Zod Validation Middleware

@middy/validator JSON Schema kullanır, bu da TypeScript entegrasyonundan yoksundur ve anlaşılmaz hata mesajları verir. Zod her iki problemi de zarif bir şekilde çözer.

Zod'u Lambda ile kullanmak ve OpenAPI entegrasyonu için kapsamlı bir rehber için Zod + OpenAPI + AWS Lambda yazımıza göz at.

Custom Zod Middleware

typescript
import { z } from 'zod'import createHttpError from 'http-errors'
const zodValidationMiddleware = <T extends z.ZodSchema>(schema: T) => {  return {    before: async (request: middy.Request) => {      const body = request.event.body
      const result = schema.safeParse(body)
      if (!result.success) {        // Zod hatalarını kullanıcı dostu mesajlara dönüştür        const errors = result.error.errors.map(err => ({          field: err.path.join('.'),          message: err.message,          code: err.code        }))
        throw createHttpError(400, 'Validation failed', { errors })      }
      // event.body'yi validate edilmiş, typed data ile değiştir      request.event.body = result.data    }  }}

Zengin Hata Mesajları

typescript
const userSchema = z.object({  email: z.string().email('Lütfen geçerli bir email adresi girin'),  age: z.number().int().min(18, 'En az 18 yaşında olmalısınız'),  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Geçersiz telefon numarası formatı'),  acceptedTerms: z.boolean().refine(val => val === true, {    message: 'Kullanım koşullarını kabul etmelisiniz'  })})
// Örnek hata response'u:// {//   "statusCode": 400,//   "message": "Validation failed",//   "errors": [//     {//       "field": "email",//       "message": "Lütfen geçerli bir email adresi girin",//       "code": "invalid_string"//     },//     {//       "field": "age",//       "message": "En az 18 yaşında olmalısınız",//       "code": "too_small"//     }//   ]// }

Advanced Validation Pattern'ları

Zod karmaşık validation senaryolarında mükemmel:

typescript
// Cross-field validationconst orderSchema = z.object({  items: z.array(z.object({    productId: z.string().uuid(),    quantity: z.number().int().positive()  })).min(1, 'Sipariş en az bir ürün içermelidir'),  total: z.number().positive()}).refine(data => {  // Total'ın ürünlerin toplamıyla eşleştiğini doğrula  const calculatedTotal = data.items.reduce((sum, item) =>    sum + (item.quantity * getPriceForProduct(item.productId)), 0  )  return Math.abs(calculatedTotal - data.total) < 0.01}, {  message: 'Sipariş toplamı ürün fiyatları ile eşleşmiyor',  path: ['total']})
// Polymorphic input'lar için discriminated unionsconst notificationSchema = z.discriminatedUnion('type', [  z.object({    type: z.literal('email'),    recipient: z.string().email(),    subject: z.string(),    body: z.string()  }),  z.object({    type: z.literal('sms'),    phoneNumber: z.string(),    message: z.string().max(160)  }),  z.object({    type: z.literal('push'),    deviceToken: z.string(),    title: z.string(),    body: z.string()  })])

Discriminated union, type alanına göre type narrowing sağlar, her variant için tam type safety verir.

Feature Flags Middleware

Feature flags, kod redeploy olmadan dinamik davranış değişikliklerini mümkün kılar. AWS AppConfig, proper caching ile enterprise-grade feature flag yönetimi sağlar.

AppConfig ile Implementation

typescript
import axios from 'axios'
interface FeatureFlagsContext {  features: Record<string, boolean>}
const featureFlagsMiddleware = (flagNames: string[]) => {  // Lambda container seviyesinde cache  let cachedFlags: Record<string, boolean> | null = null  let lastFetchTime = 0  const CACHE_TTL_MS = 30000 // 30 saniye
  return {    before: async (request: middy.Request<any, FeatureFlagsContext>) => {      const now = Date.now()
      // Cache hala fresh ise kullan      if (cachedFlags && (now - lastFetchTime) < CACHE_TTL_MS) {        request.context.features = cachedFlags        return      }
      try {        // AppConfig Lambda Extension'dan fetch et (localhost endpoint)        const response = await axios.get(          `http://localhost:2772/applications/${process.env.APPCONFIG_APP}/environments/${process.env.APPCONFIG_ENV}/configurations/${process.env.APPCONFIG_CONFIG}`,          { timeout: 3000 }        )
        const allFlags = response.data
        // Sadece istenen flag'leri çıkar        const features: Record<string, boolean> = {}        flagNames.forEach(name => {          features[name] = allFlags[name] ?? false        })
        cachedFlags = features        lastFetchTime = now        request.context.features = features      } catch (error) {        console.error('Feature flags fetch edilemedi:', error)        // Tüm flag'ler disabled olarak fail open        request.context.features = Object.fromEntries(          flagNames.map(name => [name, false])        )      }    }  }}

Advanced Pattern: User-Specific Flags

Daha sofistike senaryolar için percentage rollout ve user targeting implement edebilirsiniz:

typescript
interface FeatureFlagConfig {  enabled: boolean  rolloutPercentage?: number  targetUserIds?: string[]  targetTenants?: string[]}
const advancedFeatureFlagsMiddleware = (flagNames: string[]) => {  return {    before: async (request: middy.Request) => {      const allFlags = await fetchFlags()      const userId = request.context.userId // Auth middleware'den      const tenantId = request.event.body?.tenantId
      const features: Record<string, boolean> = {}
      for (const flagName of flagNames) {        const config: FeatureFlagConfig = allFlags[flagName]
        if (!config?.enabled) {          features[flagName] = false          continue        }
        // User targeting kontrolü        if (config.targetUserIds?.includes(userId)) {          features[flagName] = true          continue        }
        // Tenant targeting kontrolü        if (config.targetTenants?.includes(tenantId)) {          features[flagName] = true          continue        }
        // Percentage rollout kontrolü        if (config.rolloutPercentage) {          const hash = hashString(`${flagName}:${userId}`)          const userPercentage = (hash % 100) + 1          features[flagName] = userPercentage <= config.rolloutPercentage          continue        }
        features[flagName] = config.enabled      }
      request.context.features = features    }  }}

Lambda Extension Setup

Serverless configuration'da AppConfig Lambda Extension'ı yapılandır:

yaml
# serverless.yml veya SAM templateprovider:  environment:    AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS: 30    AWS_APPCONFIG_EXTENSION_POLL_TIMEOUT_MILLIS: 3000    APPCONFIG_APP: MyApplication    APPCONFIG_ENV: ${opt:stage}    APPCONFIG_CONFIG: feature-flags
  iamRoleStatements:    - Effect: Allow      Action:        - appconfig:GetConfiguration        - appconfig:GetLatestConfiguration        - appconfig:StartConfigurationSession      Resource: '*'
functions:  api:    handler: handler.main    layers:      # AppConfig Lambda Extension (region-specific ARN)      # Region'ınızdaki en son version için kontrol edin:      # https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions-versions.html      - arn:aws:lambda:us-east-1:027255383542:layer:AWS-AppConfig-Extension:207

Secrets Management Middleware

AWS Secrets Manager entegrasyonu, API throttling'i önlemek ve zero-downtime rotation'ı desteklemek için proper caching ve rotation handling gerektirir.

Basic Secrets Middleware

typescript
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'
interface SecretsContext {  secrets: Record<string, string>}
const secretsMiddleware = (secretNames: string[]) => {  // Lambda container-level cache  const secretCache = new Map<string, { value: string; fetchedAt: number }>()  const CACHE_TTL_MS = 300000 // 5 dakika
  // Client'ı invocation'lar arası yeniden kullan  const client = new SecretsManagerClient({ region: process.env.AWS_REGION })
  return {    before: async (request: middy.Request<any, SecretsContext>) => {      const secrets: Record<string, string> = {}      const now = Date.now()
      // Secret'ları parallel fetch et      await Promise.all(        secretNames.map(async (secretName) => {          // Önce cache kontrolü          const cached = secretCache.get(secretName)          if (cached && (now - cached.fetchedAt) < CACHE_TTL_MS) {            secrets[secretName] = cached.value            return          }
          try {            const command = new GetSecretValueCommand({ SecretId: secretName })            const response = await client.send(command)            const secretValue = response.SecretString || ''
            secrets[secretName] = secretValue            secretCache.set(secretName, { value: secretValue, fetchedAt: now })          } catch (error) {            console.error(`Secret fetch edilemedi ${secretName}:`, error)            // Stale olsa bile cache'lenmiş değeri kullan veya fail            const cached = secretCache.get(secretName)            if (cached) {              console.warn(`Stale cache'lenmiş secret kullanılıyor ${secretName}`)              secrets[secretName] = cached.value            } else {              throw new Error(`Gerekli secret mevcut değil ${secretName}`)            }          }        })      )
      request.context.secrets = secrets    }  }}

Parsing ile Structured Secrets

Birçok secret JSON object'tir. Type safety'yi korumak için parsing desteği ekle:

typescript
interface DatabaseConfig {  host: string  port: number  username: string  password: string  database: string}
const secretsWithParsingMiddleware = (secretConfigs: Array<{  name: string  parser?: (raw: string) => any}>) => {  return {    before: async (request: middy.Request) => {      const rawSecrets = await fetchSecrets(secretConfigs.map(c => c.name))
      const secrets: Record<string, any> = {}
      for (const config of secretConfigs) {        const rawValue = rawSecrets[config.name]        secrets[config.name] = config.parser          ? config.parser(rawValue)          : rawValue      }
      request.context.secrets = secrets    }  }}
// Kullanımexport const handler = new LambdaMiddlewareBuilder()  .withSecrets([    {      name: 'prod/database/credentials',      parser: (raw) => JSON.parse(raw) as DatabaseConfig    },    {      name: 'prod/api/keys',      parser: (raw) => JSON.parse(raw)    }  ])  .build(async (event, context) => {    const dbConfig = context.secrets['prod/database/credentials'] as DatabaseConfig    const connection = await createConnection({      host: dbConfig.host,      port: dbConfig.port,      // TypeScript structure'ı biliyor!    })  })

Tam Gerçek Dünya Örneği

Hadi her şeyi bir e-commerce API endpoint'inde birleştirelim:

typescript
// schemas/order.schema.tsimport { z } from 'zod'
export const createOrderSchema = z.object({  items: z.array(z.object({    productId: z.string().uuid(),    quantity: z.number().int().positive(),    price: z.number().positive()  })).min(1),  shippingAddress: z.object({    street: z.string().min(1),    city: z.string().min(1),    postalCode: z.string(),    country: z.string().length(2)  }),  paymentMethodId: z.string()})
// handlers/orders.tsexport const createOrder = new LambdaMiddlewareBuilder()  .withCors()  .withAuthentication()  .withValidation(createOrderSchema)  .withFeatureFlags(['expressFulfillment', 'fraudDetection', 'loyaltyProgram'])  .withSecrets(['database-credentials', 'payment-api-key'])  .build(async (event, context) => {    // TypeScript tüm bu type'ları biliyor!    const { items, shippingAddress, paymentMethodId } = event.body    const { userId } = context    const { expressFulfillment, fraudDetection, loyaltyProgram } = context.features    const dbCreds = JSON.parse(context.secrets['database-credentials'])    const paymentKey = context.secrets['payment-api-key']
    // Fraud detection enabled ise uygula    if (fraudDetection) {      const riskScore = await checkFraudRisk(userId, items, shippingAddress)      if (riskScore > 0.8) {        return {          statusCode: 400,          body: JSON.stringify({ error: 'Sipariş manuel incelemeye alındı' })        }      }    }
    // Loyalty program enabled ise puan hesapla    let loyaltyPoints = 0    if (loyaltyProgram) {      loyaltyPoints = calculateLoyaltyPoints(items)    }
    // Express fulfillment opsiyonu ile sipariş oluştur    const order = await createOrderInDatabase(dbCreds, {      userId,      items,      shippingAddress,      paymentMethodId,      expressDelivery: expressFulfillment,      loyaltyPoints    })
    // Ödemeyi işle    await processPayment(paymentKey, {      amount: order.total,      paymentMethodId    })
    return {      statusCode: 201,      body: JSON.stringify({        orderId: order.id,        estimatedDelivery: expressFulfillment          ? addDays(new Date(), 1)          : addDays(new Date(), 5),        loyaltyPointsEarned: loyaltyPoints      })    }  })

Bu örnek tüm pattern'ları birleştirmenin gücünü gösteriyor:

  • Zod ile type-safe validation
  • Gradual rollout için dinamik feature flags
  • Güvenli secrets management
  • Her yerde tam TypeScript type inference

Test Stratejileri

Builder pattern, composition ve injection yoluyla test etmeyi önemli ölçüde kolaylaştırır.

Middleware Context Mock'lama

typescript
// tests/orders.test.tsimport { createOrder } from '../handlers/orders'
describe('Create Order Handler', () => {  it('flag enabled olduğunda express fulfillment ile sipariş oluşturmalı', async () => {    const mockEvent = {      body: {        items: [{ productId: '123', quantity: 2, price: 29.99 }],        shippingAddress: {          street: '123 Main St',          city: 'Seattle',          postalCode: '98101',          country: 'US'        },        paymentMethodId: 'pm_123'      }    }
    const mockContext = {      userId: 'user-123',      features: {        expressFulfillment: true,        fraudDetection: false,        loyaltyProgram: true      },      secrets: {        'database-credentials': JSON.stringify({          host: 'localhost',          port: 5432,          username: 'test',          password: 'test'        }),        'payment-api-key': 'test-key'      }    }
    const response = await createOrder.handler(mockEvent, mockContext)
    expect(response.statusCode).toBe(201)    const body = JSON.parse(response.body)    expect(body.loyaltyPointsEarned).toBeGreaterThan(0)  })})

Test Builder Pattern

Builder pattern'ı yansıtan bir test helper oluştur:

typescript
class TestMiddlewareBuilder {  private features: Record<string, boolean> = {}  private secrets: Record<string, string> = {}  private userId = 'test-user'
  withFeature(name: string, enabled: boolean): this {    this.features[name] = enabled    return this  }
  withSecret(name: string, value: string): this {    this.secrets[name] = value    return this  }
  withUserId(id: string): this {    this.userId = id    return this  }
  buildContext() {    return {      userId: this.userId,      features: this.features,      secrets: this.secrets    }  }}
// Testlerde kullanımconst context = new TestMiddlewareBuilder()  .withFeature('expressFulfillment', true)  .withFeature('fraudDetection', false)  .withSecret('database-credentials', '{"host":"localhost"}')  .withUserId('test-123')  .buildContext()

Bu yaklaşım test setup için aynı fluent API'yi sağlar, testleri okunabilir ve sürdürülebilir kılar.

Performans Değerlendirmeleri

Performans etkilerini anlamak, bilinçli trade-off'lar yapmanıza yardımcı olur.

Cold Start Etkisi

Birden fazla projede yapılan testlere göre:

Middleware Olmadan:           50ms cold start, 5ms warmMiddy ile (5 middleware):     80ms cold start, 8ms warmBuilder + Zod + Flags ile:    95ms cold start, 10ms warm

Önemli Bağlam: Bu rakamlar küçük bundle boyutlarına sahip iyi optimize edilmiş fonksiyonları temsil ediyor. Tipik Lambda cold start'ları paket boyutu ve konfigürasyona bağlı olarak 100-400ms arasında değişir. Bu middleware yaklaşımından kaynaklanan ek 15ms bir kerelik container initialization maliyetidir. Çoğu API için, type safety ve sürdürülebilirlik faydaları göz önüne alındığında bu kabul edilebilir. Detaylı cold start optimizasyon stratejileri için AWS Lambda Cold Start Optimizasyonu yazımıza göz at.

Memory Kullanımı

  • Base Lambda + Middy: ~75MB
  • Zod Ekle: +8MB
  • AWS SDK v3 client'lar Ekle: +15MB
  • Toplam: ~98MB (minimum 128MB Lambda allocation içinde rahatça)

Optimizasyon Stratejileri

1. Connection Reuse

typescript
// AWS SDK client'larını module scope'da tutconst secretsClient = new SecretsManagerClient({ region: process.env.AWS_REGION })
const secretsMiddleware = (names: string[]) => {  return {    before: async (request) => {      // Client'ı invocation'lar arası yeniden kullan      const secrets = await fetchSecretsWithClient(secretsClient, names)      request.context.secrets = secrets    }  }}

2. Selective Middleware Sadece ihtiyaç duyduğun middleware'i dahil et:

typescript
// Hafif public endpointconst publicHandler = new LambdaMiddlewareBuilder()  .withCors()  .withValidation(schema)  .build(handler)
// Full-featured authenticated endpointconst privateHandler = new LambdaMiddlewareBuilder()  .withCors()  .withAuthentication()  .withValidation(schema)  .withFeatureFlags(['feature1', 'feature2'])  .withSecrets(['secret1'])  .build(handler)

3. Cache Warming Container initialization sırasında pre-fetch:

typescript
// module-level initializationlet warmCache: Promise<void> | null = null
if (!warmCache) {  warmCache = (async () => {    await Promise.all([      prefetchFeatureFlags(),      prefetchSecrets()    ])  })()}

Maliyet Analizi

Production deployment'lardan gerçekçi maliyet tahminlerini paylaşayım.

AWS Servis Maliyetleri

AppConfig (Feature Flags):

  • API request'ler: $0.20 per 1M request
  • Alınan configuration'lar: 0.0008perconfiguration(0.0008 per configuration (800 per 1M)
  • Lambda Extension caching ile (30s poll): ~100 request/gün/fonksiyon
  • 10 fonksiyon için maliyet: ~$0.006/ay (sadece API request'leri, minimal alınan configuration)
  • Değerlendirme: Önemli operasyonel esneklik için ihmal edilebilir maliyet

Secrets Manager:

  • Secret storage: $0.40/ay per secret
  • API request'ler: $0.05 per 10,000 request
  • 5 dakikalık caching ile: ~288 request/gün/fonksiyon
  • 5 secret, 10 fonksiyon için maliyet: ~$2.50/ay
  • Trade-off: Parameter Store'dan daha yüksek maliyet, ancak otomatik rotation desteği

Lambda Extension Overhead:

  • Extension'lar ~10-30MB memory overhead ekler
  • Execution cost üzerinde minimal etki
  • External API call'ları önemli ölçüde azaltır

Development Time Yatırımı

Initial Setup:

  • Builder pattern implementation: 4-6 saat
  • Custom Zod middleware: 2-3 saat
  • Feature flag integration: 3-4 saat
  • Secrets middleware: 2-3 saat
  • Toplam: 11-16 saat bir kerelik yatırım

Devam Eden Faydalar (birden fazla projede gözlemlenen):

  • ~%40 daha hızlı feature geliştirme (azaltılmış boilerplate)
  • Production'da yakalanan validation bug'larında önemli azalma
  • Zero-downtime feature rollout'ları
  • Builder pattern ile basitleştirilmiş testing

Yaygın Tuzaklar ve Çözümler

Implementation'ların ters gittiği durumlardan öğrendiklerimi paylaşayım.

1. Feature Flag Cache Staleness

Problem: Lambda container'lar saatlerce yaşayabilir, stale feature flag değerleri kullanır.

Çözüm: Emergency override ile TTL-based cache refresh implement et:

typescript
const CACHE_TTL = process.env.FEATURE_FLAG_TTL  ? parseInt(process.env.FEATURE_FLAG_TTL)  : 30000 // 30 saniye default
// Emergency override sağlaif (process.env.BYPASS_FLAG_CACHE === 'true') {  // Her zaman fresh flag'leri fetch et (kritik güncellemeler için)}

2. Secret Rotation Timing

Problem: Secrets Manager secret'ları rotate eder, ancak Lambda'daki cache'lenmiş değerler auth failure'lara neden olur.

Çözüm: Retry logic ile rotation-aware caching implement et:

typescript
const secretsMiddleware = () => {  return {    before: async (request) => {      try {        request.context.secrets = await fetchSecrets()      } catch (error) {        if (isAuthError(error)) {          // Cache'i temizle ve bir kez retry et          clearSecretCache()          request.context.secrets = await fetchSecrets()        } else {          throw error        }      }    }  }}

3. Middleware Sıralama Sorunları

Problem: Error handler son olmalı, ancak builder pattern middleware'i yanlış sırayla eklemeyi kolaylaştırır.

Çözüm: Builder sıralamayı internal olarak zorlar:

typescript
class SafeBuilder {  build(handler: any) {    const chain = middy(handler)
    // Core middleware belirli sırada    chain.use(httpJsonBodyParser())  // 1. Body'yi parse et    // ... validation, auth, etc    chain.use(httpErrorHandler())     // Son: Hataları handle et
    return chain  }}

Alternatif Yaklaşımlar

Bilinçli kararlar vermek için alternatifleri anlamak önemli.

Middleware execution üzerinde daha fazla kontrole veya özel performans gereksinimlerine ihtiyaç duyduğun senaryolar için, Middy'nin yeteneklerinin ötesine geçen custom middleware framework'leri inşa etme yazımızı oku.

vs. AWS Lambda Powertools

AWS Lambda Powertools:

typescript
import { Logger, Tracer, Metrics } from '@aws-lambda-powertools/logger'import { parser } from '@aws-lambda-powertools/parser'
@parser({ schema: mySchema })export const handler = async (event, context) => {  logger.info('Request işleniyor', { event })}

Karşılaştırma:

  • Powertools: Daha iyi observability, AWS-maintained, comprehensive feature'lar
  • Custom Builder: Middleware composition ile daha fazla esneklik, daha küçük bundle
  • Öneri: Her ikisini birleştir - logging/tracing için Powertools, business middleware için custom builder kullan

vs. Pure Functional Middleware

Functional Yaklaşım:

typescript
type Middleware<T> = (next: Handler<T>) => Handler<T>
const compose = <T>(...middlewares: Middleware<T>[]) =>  (handler: Handler<T>) =>    middlewares.reduceRight((next, middleware) => middleware(next), handler)
export const handler = compose(  withAuth,  withValidation(schema),  withFeatureFlags(['flag1']))(businessLogic)

Trade-off: Functional composition zarif ama context enrichment için daha az TypeScript desteği sağlıyor. Takım tercihine göre seç.

Temel Çıkarımlar

Implementation İçin

  1. Type Safety Production Sorunlarını Önler: Compile-time check'ler configuration hatalarını deployment öncesi yakalar
  2. Tutarlı Sıralama Önemlidir: Middleware execution sırasını zorlamak için builder pattern kullan
  3. Stratejik Cache Kullan: Feature flag'ler ve secret'lar uygun TTL ile cache'lenmeli
  4. Middleware'i Bağımsız Test Et: Middleware'i unit test et, chain'leri integration test et
  5. Graceful Fail: External dependency'ler için her zaman fallback davranış sağla

Mimari Kararlar İçin

  1. Basit Başla, Kasıtlı Scale Et: Temel builder ile başla, gerektiğinde feature ekle
  2. Performansı Monitor Et: Cold start'ları, warm execution'ı ve cache hit rate'leri takip et
  3. Büyüme İçin Planla: Builder pattern ad-hoc middleware composition'dan daha iyi scale olur
  4. Pattern'ları Belgele: Takım tutarlılığı için net guideline'lar oluştur
  5. Alternatifleri Değerlendir: Comprehensive observability için AWS Powertools'u değerlendir

Sağlanan Teknik İyileştirmeler

  • Azaltılmış boilerplate ile ~%40 daha hızlı feature geliştirme
  • Production'a ulaşan schema validation bug'larında önemli azalma
  • Feature flag'ler ile zero-downtime feature rollout'lar
  • Builder-based test helper'lar ile daha iyi testing ergonomi
  • Büyük Lambda codebase'lerinde iyileştirilmiş kod tutarlılığı

Sonraki Adımlar

Bu pattern'ı codebase'inize implement etmek için:

  1. Faz 1 (Hafta 1-2): Middy ve temel builder ile TypeScript projesi kur
  2. Faz 2 (Hafta 3-4): Zod validation ve feature flags middleware implement et
  3. Faz 3 (Hafta 5-6): Secrets management ekle ve ilk production fonksiyonunu migrate et
  4. Faz 4 (Hafta 7-8): Incremental rollout ve takım eğitimi

Burada keşfedilen pattern'lar, sürdürülebilir, type-safe serverless uygulamalar inşa etmek için bir temel sağlıyor. Lambda middleware ile çalışmak bana proper abstraction'lara erken yatırım yapmanın codebase scale oldukça karşılığını verdiğini öğretti.

Referanslar

İlgili Yazılar