Skip to content
~/sph.sh

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. Multi-tenant validation ve bundle size sorunları birlikte ele alındığında özel çözüm kaçınılmaz hale geldi.

Production Zorlukları

Multi-Tenant Validation Zorluğu

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ürasyonconst 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.

Teknik Impact: Ek development zamanı ve maintenance overhead'ı artıran custom validation layer.

Bundle Size Zorluğu

Middleware stack'imiz 8 farklı Middy paketine çıktığında, performance monitoring endişe verici metrikleri ortaya çıkardı:

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 transaction'lar gerçekleştiren finansal API'ler için bu performance düşüşü kullanıcı deneyimi sorunları yaratıyor. Zarif middleware soyutlaması responsiveness açısından önemli bir maliyet getiriyordu.

Team Tutarlılık Zorluğu

Farklı servisler üzerinde çalışan birden fazla developer arasında middleware kullanım pattern'leri 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 karmaşıklığı 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 zorluklar bizi middleware mimarisini tamamen yeniden düşünmeye yönlendirdi. 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ımconst 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şturconst 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'ler kritik middleware'i yanlışlıkla atlayamaz veya sıralamayı karıştıramazdı. Framework standartları 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

Benchmark sonuçları custom framework'ümüzle tüm ölçülen metriklerde anlamlı performance iyileştirmeleri gösterdi.

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'de mevcut olmayan dynamic davranışları kullananan production middleware pattern'lerinden 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'nin static configuration modelinde önemli workaround'lar gereken 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ımconst 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 middleware execution modeline göre önemli bir performance avantajı.

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ırexport 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 etconst 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'ler önemli performance iyileştirmeleri sağlayabilir ama ek development zamanı gerektirir. Bu trade-off'u 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

İnkremental migration'lar riski azaltır. Gradual, aşamalı yaklaşım çok daha güvenli oldu ve yaklaşımımızı adım adım 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, production-ready 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 yolculuk bazen en iyi çözümün kendi inşa ettiğin olduğunu gösteriyor - ama sadece compelling teknik 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

İlgili Yazılar