2025-09-04
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
Middy, küçük bir Lambda filosunun tipik middleware ihtiyaçlarını karşılar ama jenerik middleware-zinciri modelinin dengeleri, bir servis ortak bir middleware yığınını paylaşan yaklaşık 50 fonksiyona ulaştığında ölçülebilir hale gelir: çağrı başına ek yük, middleware zincirinin cold-start maliyeti ve ortak bir wrapper’ın başka türlü bağlantısız fonksiyonlar arasında yarattığı kenetlenme. Bu ölçekte soru şuna döner: Middy’nin soyutlamaları üzerine katmanlamaya devam mı edilir, AWS Lambda PowerTools ile değiştirilir mi, yoksa yalnızca filonun gerçekten kullandığı hook’lar için ödeme yapan projeye özel bir middleware framework’ü mü yazılır.
Bu yazı, Middy serisinin ikinci parçasıdır. Middy’nin varsayılan modelinin ölçek limitlerini, cold-start ve maliyet hesabını, tipik bir serverless backend’in ihtiyaç duyduğu hook’ları sunan projeye özel bir middleware framework tasarımını ve Middy ile sarmalanmış bir Lambda’dan özel middleware ile sarmalanmış bir Lambda’ya sözleşmeyi bozmadan geçiş yolunu ele alır.
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ı:
// 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.
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:
// 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:
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:
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:
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’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:
| Metrik | Middy + 5 Middleware | Custom Framework | İyileştirme |
|---|---|---|---|
| Bundle Size | 1.8MB | 0.6MB | %67 daha küçük |
| Cold Start | 980ms | 320ms | %67 daha hızlı |
| Warm Request | 45ms | 28ms | %38 daha hızlı |
| Memory Usage | 128MB | 94MB | %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ı:
export const handler = middy(businessLogic)
.use(httpJsonBodyParser())
.use(httpCors({ origin: true }))
.use(validator({ eventSchema: schema }))
.use(httpErrorHandler())
.use(httpSecurityHeaders())
Custom Framework:
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
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
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 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
// 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
// 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’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:
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:
- Middy ile başla - Proven, production-ready ve middleware pattern’lerini öğrenmek için harika
- Optimize etmeden önce ölç - Performance data’nın kararlarını yönlendirmesine izin ver, varsayımların değil
- Team tutarlılığı framework seçiminden daha önemli - Standartlar ve enforcement kritik
- Custom her zaman daha iyi değil - Maintenance maliyetleri ve team expertise’ini hesaba kat
- 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
Serideki tüm yazılar
İlgili yazılar
Middy'nin middleware kalıplarıyla Lambda geliştirmesini nasıl dönüştürdüğünü, tekrarlayan şablonlardan temiz, sürdürülebilir serverless fonksiyonlara geçişi keşfedin
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.
Serverless ortamlarda Node.js'den Go'ya geçiş sürecinden gerçek deneyimler: performans kazanımları, takım zorlukları ve pratik karar çerçeveleri.
Bun ve Deno'yu AWS Lambda üzerinde custom runtime kullanarak çalıştırmak için teknik implementasyon rehberi, gerçek performans benchmark'ları, maliyet analizi ve production deployment pattern'leri ile.
CloudEvents spesifikasyonu ve TypeScript SDK'sını serverless projelerde kullanmak için pratik bir kılavuz. AWS Lambda, EventBridge ve diğer event-driven sistemlerde standardize edilmiş eventler oluşturmayı, parse etmeyi ve validate etmeyi öğrenin.