Middy Yeterli Olmadığında - Özel Lambda Middleware Framework'leri Geliştirme

Bizi Middy'nin sınırlarının ötesine iten production zorluklarını ve performance ile scale için optimize edilmiş özel middleware framework'ümüzü nasıl geliştirdiğimizi keşfedin

Middy Yeterli Olmadığında - Özel Lambda Middleware Framework'leri Geliştirme#

1. Bölüm'de Middy'nin temiz middleware pattern'leri ile Lambda development'ı nasıl dönüştürdüğünü gördük. Peki 50+ Lambda fonksiyonunu yönetirken Middy'nin sınırları belirmeye başladığında ne olur?

Bu durumla tam olarak büyük bir platform migration sırasında karşılaştık. Middy'nin zarafeti ile başlayan aşk hikayesi, scaling challenge'ları, performance darboğazları ve sonunda kendi middleware framework'ümüzü geliştirme kararı ile devam etti.

Kırılma Noktaları - Production'dan War Story'ler#

Multi-Tenant Validation Krizi#

Fintech platformumuz multiple client'lara hizmet veriyordu, her birinin tamamen farklı validation rule'ları vardı. Customer A UK postal code'ları istiyordu, Customer B German VAT validation'a ihtiyaç duyuyordu ve Customer C tamamen custom business rule'ları vardı.

Middy'nin static middleware yaklaşımı duvara çarptı:

TypeScript
// Middy ile problem - static konfigürasyon
const schema = getSchemaForTenant(tenantId) // Bunu dynamic yapmamız lazım!
.use(validator({ eventSchema: schema })) // Ama bu static olmak zorunda

Runtime'da dynamic schema generation'a ihtiyacımız vardı, ama Middy middleware'leri initialization time'da konfigüre ediyor. Workaround? Handler'larımızın her yerine saçılan kötü conditional logic'ler, temiz middleware separation'ın tüm amacını bozuyordu.

Business Impact: Üç günlük development delay'i ve maintain etmek istemediğimiz custom validation layer.

Bundle Size Kabusu#

Middleware stack'imiz 8 farklı Middy paketine çıktığında, quarterly performance review sırasında alarm verici bir şey oldu:

Performance Metrikleri:

  • Bundle size: 2MB (400KB'den yükseldi)
  • Cold start time: 1.2 saniye (hedef: <500ms)
  • Memory usage: 128MB baseline
  • First response time: 1.8 saniye

High-frequency trading API'miz için bu felaketti. Her milisaniyelik latency kayıp müşteri demekti. Business team'i "zarif" middleware'imizin müşteri kaybettirdiğini öğrendiğinde memnun olmadı.

Team Tutarlılık Problemi#

12 developer'ın farklı servisler üzerinde çalıştığı ortamda, middleware kullanımı vahşi bir şekilde tutarsız hale geldi:

TypeScript
// Developer A'nın yaklaşımı
export const handler = middy(businessLogic)
  .use(httpJsonBodyParser())
  .use(validator())
  .use(httpErrorHandler())

// Developer B'nin yaklaşımı (sıralama farklı!)
export const handler = middy(businessLogic)  
  .use(httpErrorHandler()) // Error handling önce mi?
  .use(httpJsonBodyParser())
  .use(validator())

// Developer C'nin yaklaşımı
export const handler = middy(businessLogic)
  .use(customAuth()) // Team-specific middleware
  .use(httpJsonBodyParser())
  // Validator hiç yok!

Sonuç: Production incident'ları, debugging kabusu ve servisler arasında farklı şekilde çalışan error handling. Convention'lar değil, enforcement'a ihtiyacımız vardı.

Custom Middleware Framework Tasarımı#

Bu acı noktaları bizi middleware'i tamamen yeniden düşünmeye zorladı. Custom framework'ümüz üç temel prensibe odaklandı:

1. Performance-First Mimari#

Maksimum hız için lightweight context sistemi ve pre-compiled middleware chain'leri inşa ettik:

TypeScript
interface LightweightContext {
  event: any
  context: any
  response?: any
  metadata: Map<string, any> // Memory efficient storage
  startTime: number
}

type MiddlewareHandler = (
  ctx: LightweightContext, 
  next: () => Promise<void>
) => Promise<void>

class CustomMiddlewareEngine {
  private middlewares: MiddlewareHandler[] = []
  private isCompiled = false
  private compiledChain?: (ctx: LightweightContext) => Promise<void>
  
  use(middleware: MiddlewareHandler): this {
    if (this.isCompiled) {
      throw new Error('Cannot add middleware after compilation')
    }
    this.middlewares.push(middleware)
    return this
  }
  
  // Performance için middleware chain'ini pre-compile et
  private compile(): void {
    const chain = this.middlewares.reduceRight(
      (next, middleware) => (ctx: LightweightContext) => 
        middleware(ctx, () => next(ctx)),
      () => Promise.resolve()
    )
    this.compiledChain = chain
    this.isCompiled = true
  }
  
  async execute(event: any, context: any): Promise<any> {
    if (!this.isCompiled) this.compile()
    
    const ctx: LightweightContext = {
      event,
      context,
      metadata: new Map(),
      startTime: Date.now()
    }
    
    try {
      await this.compiledChain!(ctx)
      return ctx.response
    } catch (error) {
      return this.handleError(error, ctx)
    }
  }
}

Temel optimizasyon: Her request'te build etmek yerine middleware chain'ini pre-compile ediyoruz. Bu tek değişiklik middleware overhead'ımızı %40 azalttı.

2. Dynamic Configuration Desteği#

Multi-tenant validation problemimiz için runtime'da configuration resolve eden dynamic middleware geliştirdik:

TypeScript
interface DynamicValidationOptions {
  getSchema: (ctx: LightweightContext) => Promise<any>
  cacheKey?: (ctx: LightweightContext) => string
}

const dynamicValidator = (options: DynamicValidationOptions): MiddlewareHandler => {
  const schemaCache = new Map<string, any>()
  
  return async (ctx, next) => {
    let schema: any
    
    if (options.cacheKey) {
      const key = options.cacheKey(ctx)
      schema = schemaCache.get(key)
      
      if (!schema) {
        schema = await options.getSchema(ctx)
        schemaCache.set(key, schema)
      }
    } else {
      schema = await options.getSchema(ctx)
    }
    
    const isValid = validateAgainstSchema(ctx.event, schema)
    if (!isValid) {
      throw new ValidationError('Invalid request data')
    }
    
    await next()
  }
}

// Multi-tenant desteği ile kullanım
const handler = new CustomMiddlewareEngine()
  .use(dynamicValidator({
    getSchema: async (ctx) => {
      const tenantId = ctx.event.pathParameters?.tenantId
      return await getTenantSchema(tenantId)
    },
    cacheKey: (ctx) => `tenant:${ctx.event.pathParameters?.tenantId}`
  }))

Bu, intelligent caching ile performance'ı korurken multi-tenant validation problemimizi çözdü.

3. Team Convention Enforcement#

Developer'ların convention'ları takip etmelerini ummak yerine, enforcement'ı framework'ün içine inşa ettik:

TypeScript
interface TeamStandards {
  requiredMiddlewares: string[]
  forbiddenMiddlewares?: string[]
  middlewareOrder: string[]
}

const teamStandardsEnforcer = (standards: TeamStandards): MiddlewareHandler => {
  return async (ctx, next) => {
    const appliedMiddlewares = ctx.metadata.get('middlewares') || []
    
    // Gerekli middleware'lerin mevcut olduğunu doğrula
    for (const required of standards.requiredMiddlewares) {
      if (!appliedMiddlewares.includes(required)) {
        throw new Error(`Required middleware missing: ${required}`)
      }
    }
    
    await next()
  }
}

// Standardize handler factory oluştur
const createStandardHandler = (businessLogic: Function) => {
  return new CustomMiddlewareEngine()
    .use(teamStandardsEnforcer({
      requiredMiddlewares: ['auth', 'validation', 'errorHandler'],
      middlewareOrder: ['auth', 'validation', 'businessLogic', 'errorHandler']
    }))
    .use(authMiddleware())
    .use(validationMiddleware())
    .use(wrapBusinessLogic(businessLogic))
    .use(errorHandlerMiddleware())
}

Artık team'imiz kritik middleware'i yanlışlıkla atlayamaz veya sıralamayı karıştıramazdı. Framework standartlarımızı enforce ediyordu.

Performance Benchmarking - Rakamlar#

İdentik functionality kullanarak Middy ile custom framework'ümüzü kapsamlı şekilde benchmark'ladık:

Test Senaryosu:

  • Auth, validation, error handling ile basit HTTP API
  • 1000 cold start, 10.000 warm request
  • Node.js 18 runtime, 1024MB memory

Sonuçlar:

MetrikMiddy + 5 MiddlewareCustom Frameworkİyileştirme
Bundle Size1.8MB0.6MB%67 daha küçük
Cold Start980ms320ms%67 daha hızlı
Warm Request45ms28ms%38 daha hızlı
Memory Usage128MB94MB%27 daha az

Rakamlar kendileri konuşuyordu. Custom framework'ümüz sadece daha hızlı değil—dramatik olarak daha hızlıydı.

Code Karşılaştırması#

Middy Yaklaşımı:

TypeScript
export const handler = middy(businessLogic)
  .use(httpJsonBodyParser())
  .use(httpCors({ origin: true }))
  .use(validator({ eventSchema: schema }))
  .use(httpErrorHandler())
  .use(httpSecurityHeaders())

Custom Framework:

TypeScript
const handler = new CustomMiddlewareEngine()
  .use(jsonParser())
  .use(corsHandler({ origin: true }))
  .use(requestValidator(schema))
  .use(businessLogicWrapper(businessLogic))
  .use(errorHandler())
  .use(securityHeaders())

Benzer API, dramatik olarak farklı performance karakteristikleri.

Gerçek Dünya Custom Middleware Örnekleri#

İşte Middy ile imkansız olacak production middleware'lerden bazıları:

1. Exponential Backoff ile Circuit Breaker#

TypeScript
interface CircuitBreakerOptions {
  failureThreshold: number
  recoveryTimeout: number
  monitor?: (state: 'open' | 'closed' | 'half-open') => void
}

const circuitBreaker = (options: CircuitBreakerOptions): MiddlewareHandler => {
  let failures = 0
  let lastFailure = 0
  let state: 'open' | 'closed' | 'half-open' = 'closed'
  
  return async (ctx, next) => {
    const now = Date.now()
    
    // Recovery denemesi yapmalı mıyız kontrol et
    if (state === 'open' && now - lastFailure > options.recoveryTimeout) {
      state = 'half-open'
      options.monitor?.(state)
    }
    
    // Circuit açıksa request'leri blokla
    if (state === 'open') {
      throw new Error('Circuit breaker is open - service temporarily unavailable')
    }
    
    try {
      await next()
      
      // Başarı - failure'ları resetle
      if (failures > 0) {
        failures = 0
        state = 'closed'
        options.monitor?.(state)
      }
      
    } catch (error) {
      failures++
      lastFailure = now
      
      if (failures >= options.failureThreshold) {
        state = 'open'
        options.monitor?.(state)
      }
      
      throw error
    }
  }
}

Bu middleware downstream servisleri cascading failure'lardan otomatik olarak koruyor—Middy'de önemli workaround'lar gerekecek bir şey.

2. Invalidation ile Smart Caching#

TypeScript
interface CacheOptions {
  ttl: number
  keyGenerator: (ctx: LightweightContext) => string
  shouldCache: (ctx: LightweightContext) => boolean
  invalidateOn?: string[]
}

const smartCache = (options: CacheOptions): MiddlewareHandler => {
  const cache = new Map<string, { data: any, expires: number }>()
  
  return async (ctx, next) => {
    const cacheKey = options.keyGenerator(ctx)
    const now = Date.now()
    
    // Cache hit kontrolü
    if (options.shouldCache(ctx)) {
      const cached = cache.get(cacheKey)
      if (cached && cached.expires > now) {
        ctx.response = cached.data
        ctx.metadata.set('cache', 'hit')
        return // Kalan middleware'leri atla
      }
    }
    
    await next()
    
    // Response'u cache'le
    if (ctx.response && options.shouldCache(ctx)) {
      cache.set(cacheKey, {
        data: ctx.response,
        expires: now + options.ttl
      })
      ctx.metadata.set('cache', 'miss')
    }
  }
}

// Intelligent caching ile kullanım
const handler = new CustomMiddlewareEngine()
  .use(smartCache({
    ttl: 5 * 60 * 1000, // 5 dakika
    keyGenerator: (ctx) => `user:${ctx.event.pathParameters?.userId}`,
    shouldCache: (ctx) => ctx.event.httpMethod === 'GET'
  }))
  .use(businessLogicWrapper(getUserProfile))

Bu middleware cache hit'lerde tüm request pipeline'ını short-circuit edebiliyor—Middy'nin linear yaklaşımıyla imkansız olan büyük bir performance kazancı.

Migration Stratejisi - Middy'den Custom'a#

Production'da Middy'den custom framework'ümüze geçiş dikkatli, aşamalı bir yaklaşım gerektiriyordu:

Phase 1: Hybrid Yaklaşım#

TypeScript
// Custom middleware'i mevcut Middy ile karıştır
export const handler = middy(businessLogic)
  .use(customPerformanceMiddleware()) // Bizim custom
  .use(httpJsonBodyParser())          // Middy
  .use(customValidation())            // Bizim custom
  .use(httpErrorHandler())            // Middy

Phase 2: Feature Parity#

TypeScript
// Tüm Middy middleware'leri için custom equivalent'lar inşa et
const customJsonParser = (): MiddlewareHandler => {
  return async (ctx, next) => {
    if (ctx.event.body && typeof ctx.event.body === 'string') {
      try {
        ctx.event.body = JSON.parse(ctx.event.body)
      } catch (error) {
        throw new Error('Invalid JSON body')
      }
    }
    await next()
  }
}

Phase 3: Performance Optimization#

Tüm middleware'ler port edildikten sonra, specific use case'lerimiz için optimize ettik ve daha önce gösterilen %67'lik performance iyileştirmesini elde ettik.

Phase 4: Team Training & Standards#

Son aşama team'i train etmek ve custom framework'ümüz etrafında yeni development standartları oluşturmaktı.

Ne Zaman Custom vs Middy Seçmeli#

Deneyimimize dayanarak, işte karar matrisi:

Middy'yi Seç:#

  • ✅ Team middleware pattern'leri konusunda yeni
  • ✅ Standard use case'ler (HTTP API'ler, basic validation)
  • ✅ Hızlı development öncelik
  • ✅ Bundle size <1MB kabul edilebilir
  • ✅ Cold start <1s kabul edilebilir
  • ✅ Custom çözümler için sınırlı development kaynakları

Custom Framework Seç:#

  • ✅ Performance kritik (<500ms cold start gerekli)
  • ✅ Dynamic davranış gerektiren karmaşık business rule'ları
  • ✅ Team middleware expertise'ine sahip
  • ✅ Specific compliance/güvenlik gereksinimleri
  • ✅ Large-scale uygulamalar (50+ fonksiyon)
  • ✅ Team standardization ve enforcement ihtiyacı

Hybrid Yaklaşım:#

  • ✅ Çözümler arası migration aşaması
  • ✅ Fonksiyon başına farklı performance gereksinimleri
  • ✅ Productivity korurken custom pattern'leri öğrenme

Production'dan Çıkarılan Dersler#

1. Performance vs Developer Experience#

Custom framework'ümüz 3x daha hızlıydı ama develop etmesi 2x uzun sürdü. Bu trade-off'u business gereksinimlerinize ve team yeteneklerinize göre değerlendirin.

2. Team Adoption Kritik#

En iyi framework bile team'iniz adopt edemezse değersizdir. Change management ve training, teknik çözüm kadar önemli.

3. Maintenance Overhead Gerçek#

Custom çözümler custom maintenance demek. Middy'nin community desteğinin gerçek değeri var—bunu kararınıza dahil edin.

4. Gradual Migration Daha Güvenli#

Big-bang migration'lar riskli. Gradual, aşamalı yaklaşım çok daha güvenli oldu ve yaklaşımımızı incrementally validate etmemize izin verdi.

Custom Middleware Test Etme#

Custom framework'ümüzü test etmek farklı bir yaklaşım gerektiriyordu:

TypeScript
describe('Custom Middleware Framework', () => {
  test('should execute middleware chain in order', async () => {
    const executionOrder: string[] = []
    
    const middleware1 = async (ctx: any, next: Function) => {
      executionOrder.push('before-1')
      await next()
      executionOrder.push('after-1')
    }
    
    const middleware2 = async (ctx: any, next: Function) => {
      executionOrder.push('before-2')
      await next()
      executionOrder.push('after-2')
    }
    
    const engine = new CustomMiddlewareEngine()
      .use(middleware1)
      .use(middleware2)
    
    await engine.execute({}, {})
    
    expect(executionOrder).toEqual([
      'before-1', 'before-2', 'after-2', 'after-1'
    ])
  })
  
  test('should handle circuit breaker correctly', async () => {
    const failingMiddleware = async () => {
      throw new Error('Service unavailable')
    }
    
    const engine = new CustomMiddlewareEngine()
      .use(circuitBreaker({ failureThreshold: 2, recoveryTimeout: 1000 }))
      .use(failingMiddleware)
    
    // İlk failure
    await expect(engine.execute({}, {})).rejects.toThrow('Service unavailable')
    
    // İkinci failure - circuit'i açmalı
    await expect(engine.execute({}, {})).rejects.toThrow('Service unavailable')
    
    // Üçüncü request - circuit breaker tarafından bloklanmalı
    await expect(engine.execute({}, {})).rejects.toThrow('Circuit breaker is open')
  })
})

Production Checklist#

Custom middleware framework'ünü production'a almadan önce:

  • Performance benchmark'ları dokümente edildi ve validate edildi
  • Error handling tüm senaryolar için kapsamlı
  • Monitoring ve alerting entegre edildi
  • Team training hands-on alıştırmalarla tamamlandı
  • Dokümantasyon güncel ve erişilebilir
  • Rollback planı test edildi ve hazır
  • A/B testing capability implement edildi
  • Güvenlik review penetration testing ile geçildi
  • Gerçekçi koşullarda load testing tamamlandı

Sonuç#

Middy çoğu Lambda uygulaması için mükemmel bir başlangıç noktası. Ama scale'de çalışırken, karmaşık business requirement'larla dealing ederken veya strict performance kısıtlamaları ile karşı karşıyayken, custom middleware framework transformative olabiliyor.

Anahtar Çıkarımlar:

  1. Middy ile başla - Proven, battle-tested ve middleware pattern'lerini öğrenmek için harika
  2. Optimize etmeden önce ölç - Performance data'nın kararlarını yönlendirmesine izin ver, varsayımların değil
  3. Team tutarlılığı framework seçiminden daha önemli - Standartlar ve enforcement kritik
  4. Custom her zaman daha iyi değil - Maintenance maliyetleri ve team expertise'ini hesaba kat
  5. Migration dikkatli planlama gerektirir - Gradual yaklaşımlar riski azaltır ve validation sağlar

Middy'den custom framework'e yolculuğumuz bize bazen en iyi çözümün kendi inşa ettiğin olduğunu öğretti—ama sadece compelling business nedenlerin ve bunu iyi execute edecek team expertise'inin olduğu durumlarda.

Middy'den öğrendiğimiz middleware pattern'leri, specific ihtiyaçlarımıza daha uygun bir şeyin temeli haline geldi. Middy ile devam edin veya kendiniz inşa edin, temiz middleware design prensipleri serverless journey'inizde size iyi hizmet edecek.

AWS Lambda Middleware Uzmanlığı

Middy temellerinden production ölçeği Lambda uygulamaları için özel middleware framework'leri oluşturmaya

İlerleme2/2 yazı tamamlandı

Bu Serideki Tüm Yazılar

Bölüm 2: Production için Özel Middleware Framework'leri Geliştirme
Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts