2025-11-05
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:
// Hata yapmak çok kolay - compile-time checking yok
export 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:
- Compile-time type safety: Configuration hatalarını deployment öncesi yakala
- Enforced middleware ordering: Tüm fonksiyonlarda tutarlı execution
- Daha iyi validation hataları: Schema validation’dan net, actionable mesajlar
- Feature flag integration: Kod deployment olmadan feature toggle
- Secrets management: Cache’lenmiş, rotation-aware secret erişimi
- Discoverable API: Autocomplete ve type hints ile developer guidance
- 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
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
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
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ı
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:
// Cross-field validation
const 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 unions
const 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
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:
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:
# serverless.yml veya SAM template
provider:
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
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:
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ım
export 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:
// schemas/order.schema.ts
import { 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.ts
export 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
// tests/orders.test.ts
import { 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:
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ım
const 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 warm
Middy ile (5 middleware): 80ms cold start, 8ms warm
Builder + 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
// AWS SDK client'larını module scope'da tut
const 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:
// Hafif public endpoint
const publicHandler = new LambdaMiddlewareBuilder()
.withCors()
.withValidation(schema)
.build(handler)
// Full-featured authenticated endpoint
const privateHandler = new LambdaMiddlewareBuilder()
.withCors()
.withAuthentication()
.withValidation(schema)
.withFeatureFlags(['feature1', 'feature2'])
.withSecrets(['secret1'])
.build(handler)
3. Cache Warming Container initialization sırasında pre-fetch:
// module-level initialization
let 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: 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:
const CACHE_TTL = process.env.FEATURE_FLAG_TTL
? parseInt(process.env.FEATURE_FLAG_TTL)
: 30000 // 30 saniye default
// Emergency override sağla
if (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:
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:
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:
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:
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
- Type Safety Production Sorunlarını Önler: Compile-time check’ler configuration hatalarını deployment öncesi yakalar
- Tutarlı Sıralama Önemlidir: Middleware execution sırasını zorlamak için builder pattern kullan
- Stratejik Cache Kullan: Feature flag’ler ve secret’lar uygun TTL ile cache’lenmeli
- Middleware’i Bağımsız Test Et: Middleware’i unit test et, chain’leri integration test et
- Graceful Fail: External dependency’ler için her zaman fallback davranış sağla
Mimari Kararlar İçin
- Basit Başla, Kasıtlı Scale Et: Temel builder ile başla, gerektiğinde feature ekle
- Performansı Monitor Et: Cold start’ları, warm execution’ı ve cache hit rate’leri takip et
- Büyüme İçin Planla: Builder pattern ad-hoc middleware composition’dan daha iyi scale olur
- Pattern’ları Belgele: Takım tutarlılığı için net guideline’lar oluştur
- 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:
- Faz 1 (Hafta 1-2): Middy ve temel builder ile TypeScript projesi kur
- Faz 2 (Hafta 3-4): Zod validation ve feature flags middleware implement et
- Faz 3 (Hafta 5-6): Secrets management ekle ve ilk production fonksiyonunu migrate et
- 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
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
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
AI agent geliştirmek için TypeScript SDK'larının pratik karşılaştırması - Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu. Kod örnekleri, karar frameworkleri ve production patternleri içeriyor.
Effect'i anlamak, adım adım öğrenmek ve AWS Lambda ile entegre etmek için kapsamlı bir rehber. Gerçek kod örnekleri, yaygın hatalar ve üretim kullanımından pratik desenler içerir.
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.