AWS Lambda Middleware ile Middy - Temiz Kod ve En İyi Uygulamalar
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
AWS Lambda Middleware ile Middy - Temiz Kod ve En İyi Uygulamalar#
Şöyle bir durumu hayal edin: team'inizdeki Lambda fonksiyonlarını inceliyorsunuz ve her birinin aynı 40 satır validation, error handling ve CORS setup'ı ile başladığını görüyorsunuz. Tanıdık geliyor mu? Ben de orada bulundum, copy-paste festivalinin yanlış gittiği bir duruma bakıyordum.
Bu, fintech uygulaması için 30+ Lambda fonksiyonunu yönettiğimiz dönemde yaşadığımız gerçekti. Her endpoint authentication, input validation, proper error responses ve security headers gerekiyordu. Bu boilerplate'i tekrar tekrar yazmak sadece sıkıcı değildi - aynı zamanda bir maintenance kabusu ve subtle bug'ların üreme alanı haline geliyordu.
İşte o zaman Middy'yi keşfettik ve açıkçası, Lambda fonksiyonlarını yazma şeklimizi tamamen değiştirdi.
Middy Nedir?#
Middy'yi, Express veya Koa'dan bildiğin middleware sistemi gibi düşün, ama özellikle AWS Lambda için tasarlanmış. Business logic'in merkezde oturduğu, etrafını sıkıcı ama gerekli işleri halleden yeniden kullanılabilir middleware'lerin çevrelediği soğan katmanı yaklaşımını benimsiyor.
Her şeyi handler fonksiyonuna tıkıştırmak yerine, Middy temiz, odaklanmış fonksiyonlar compose etmene izin veriyor:
// Middy'siz - Eski yöntem
export const handler = async (event: APIGatewayProxyEvent) => {
try {
// JSON body'yi parse et
let body
try {
body = JSON.parse(event.body || '{}')
} catch (e) {
return {
statusCode: 400,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Invalid JSON' })
}
}
// Input'u validate et
if (!body.name || typeof body.name !== 'string') {
return {
statusCode: 400,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Name is required' })
}
}
// Security header'ları ekle
const headers = {
'Access-Control-Allow-Origin': '*',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY'
}
// Nihayet, business logic'in
const greeting = `Hello, ${body.name}!`
return {
statusCode: 200,
headers,
body: JSON.stringify({ message: greeting })
}
} catch (error) {
console.error('Error:', error)
return {
statusCode: 500,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Internal server error' })
}
}
}
// Middy ile - Temiz ve odaklanmış
import middy from '@middy/core'
import httpJsonBodyParser from '@middy/http-json-body-parser'
import httpErrorHandler from '@middy/http-error-handler'
import httpCors from '@middy/http-cors'
import httpSecurityHeaders from '@middy/http-security-headers'
import validator from '@middy/validator'
import { transpileSchema } from '@middy/validator/transpile'
// Saf business logic
const baseHandler = async (event: APIGatewayProxyEvent) => {
const { name } = event.body as { name: string }
return {
statusCode: 200,
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString()
})
}
}
const schema = {
type: 'object',
properties: {
body: {
type: 'object',
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 }
},
required: ['name']
}
}
}
export const handler = middy(baseHandler)
.use(httpJsonBodyParser())
.use(validator({ eventSchema: transpileSchema(schema) }))
.use(httpCors({ origin: '*' }))
.use(httpSecurityHeaders())
.use(httpErrorHandler())
Fark çarpıcı. Business logic'in şovun yıldızı haline geliyor, tüm HTTP concerns'leri ise battle-tested middleware'ler tarafından tutarlı şekilde handle ediliyor.
Temel Middy Middleware'leri#
Onlarca Lambda fonksiyonuyla çalıştıktan sonra, gerekli gördüğüm middleware'ler şunlar:
HTTP Temel İşlevler#
import httpJsonBodyParser from '@middy/http-json-body-parser' // JSON body'leri parse eder
import httpErrorHandler from '@middy/http-error-handler' // Error'ları HTTP response'lara dönüştürür
import httpEventNormalizer from '@middy/http-event-normalizer' // API Gateway event'lerini normalize eder
import httpResponseSerializer from '@middy/http-response-serializer' // Response serialization'ı halleder
Güvenlik ve CORS#
import httpSecurityHeaders from '@middy/http-security-headers' // Güvenlik header'ları ekler
import httpCors from '@middy/http-cors' // CORS'u halleder
Validation#
import validator from '@middy/validator' // JSON Schema validation
AWS Servis Entegrasyonları#
import ssm from '@middy/ssm' // AWS Systems Manager parametreleri
import secretsManager from '@middy/secrets-manager' // AWS Secrets Manager
import warmup from '@middy/warmup' // Lambda warmup handling
Gerçek Dünya Örneği: Kullanıcı Kayıt API'si#
Bu middleware'lerin production senaryosunda nasıl bir araya geldiğini göstereyim. İşte validation, security ve error case'leri gracefully handle eden bir kullanıcı kayıt endpoint'i:
import middy from '@middy/core'
import httpJsonBodyParser from '@middy/http-json-body-parser'
import httpErrorHandler from '@middy/http-error-handler'
import httpSecurityHeaders from '@middy/http-security-headers'
import httpCors from '@middy/http-cors'
import validator from '@middy/validator'
import { transpileSchema } from '@middy/validator/transpile'
import { createError } from '@middy/util'
interface UserRegistration {
email: string
password: string
firstName: string
lastName: string
}
const registerUser = async (event: APIGatewayProxyEvent) => {
const userData = event.body as UserRegistration
// Kullanıcının daha önce var olup olmadığını kontrol et
const existingUser = await getUserByEmail(userData.email)
if (existingUser) {
throw createError(409, 'User already exists', {
type: 'UserAlreadyExists'
})
}
// Yeni kullanıcı oluştur
const hashedPassword = await hashPassword(userData.password)
const newUser = await createUser({
...userData,
password: hashedPassword
})
// Hoşgeldin email'i gönder (fire and forget)
sendWelcomeEmail(newUser.email, newUser.firstName).catch(
error => console.error('Failed to send welcome email:', error)
)
return {
statusCode: 201,
body: JSON.stringify({
id: newUser.id,
email: newUser.email,
firstName: newUser.firstName,
lastName: newUser.lastName,
createdAt: newUser.createdAt
})
}
}
const registrationSchema = {
type: 'object',
properties: {
body: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
maxLength: 254
},
password: {
type: 'string',
minLength: 8,
maxLength: 128,
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]'
},
firstName: {
type: 'string',
minLength: 1,
maxLength: 50
},
lastName: {
type: 'string',
minLength: 1,
maxLength: 50
}
},
required: ['email', 'password', 'firstName', 'lastName']
}
}
}
export const handler = middy(registerUser)
.use(httpJsonBodyParser())
.use(validator({ eventSchema: transpileSchema(registrationSchema) }))
.use(httpCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') ?? ['http://localhost:3000'],
credentials: true
}))
.use(httpSecurityHeaders({
hsts: {
maxAge: 31536000,
includeSubDomains: true
}
}))
.use(httpErrorHandler({
logger: console.error
}))
Bu tek middleware chain'i hallediyor:
- Error handling ile JSON parsing
- Kapsamlı input validation (password complexity dahil)
- Configurable origin'lerle CORS header'ları
- Koruma için security header'ları
- Proper HTTP error response'ları
- Request logging
Business logic'in temiz ve test edilebilir kalıyor, tüm HTTP concerns'leri ise tutarlı şekilde handle ediliyor.
Özel Middleware Yazma#
Bazen uygulamanıza özel bir şeye ihtiyacınız olur. Pattern'i anladığınızda custom middleware oluşturmak oldukça kolay:
import { MiddlewareObj } from '@middy/core'
interface RequestTimingOptions {
logSlowRequests?: boolean
slowRequestThreshold?: number
}
export const requestTiming = (
options: RequestTimingOptions = {}
): MiddlewareObj => {
const { logSlowRequests = true, slowRequestThreshold = 1000 } = options
return {
before: async (request) => {
// Timing'i başlat
request.internal = request.internal || {}
request.internal.startTime = Date.now()
},
after: async (request) => {
if (request.internal?.startTime) {
const duration = Date.now() - request.internal.startTime
// Response'a timing header'ı ekle
if (request.response && typeof request.response === 'object') {
const response = request.response as any
response.headers = {
...response.headers,
'X-Execution-Time': duration.toString()
}
}
// Yavaş request'leri logla
if (logSlowRequests && duration > slowRequestThreshold) {
console.warn(`Slow request detected: ${duration}ms`, {
functionName: request.context.functionName,
requestId: request.context.awsRequestId,
duration
})
}
}
},
onError: async (request) => {
if (request.internal?.startTime) {
const duration = Date.now() - request.internal.startTime
console.error(`Request failed after ${duration}ms`, {
error: request.error?.message,
duration,
requestId: request.context.awsRequestId
})
}
}
}
}
// Kullanım
export const handler = middy(baseHandler)
.use(requestTiming({ slowRequestThreshold: 500 }))
.use(httpJsonBodyParser())
.use(httpErrorHandler())
Bu custom middleware, response'lara execution timing ekliyor ve yavaş request'leri otomatik olarak logluyor. Pattern temiz: before
handler'ından önce çalışır, after
başarı durumunda, onError
ise hata durumunda çalışır.
Production En İyi Uygulamaları#
Production'da Middy çalıştırırken öğrendiklerim:
1. Sıralama Önemli#
Middleware çalışma sırası kritik. Yanlış sıralama yüzünden subtle bug'lar gördüm:
// Yanlış sıralama - validator body parsing'den önce çalışıyor
export const handler = middy(baseHandler)
.use(validator({ eventSchema: schema })) // Bu fail olacak!
.use(httpJsonBodyParser())
.use(httpErrorHandler())
// Doğru sıralama
export const handler = middy(baseHandler)
.use(httpJsonBodyParser()) // Önce parse et
.use(validator({ eventSchema: schema })) // Sonra validate et
.use(httpErrorHandler()) // Son olarak error'ları handle et
2. Type Safety Şart#
Her zaman proper TypeScript type'ları kullan:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
const typedHandler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
// TypeScript error'ları compile time'da yakalayacak
const body = event.body as UserRegistration
// ... geri kalan logic
}
3. Error Handling Stratejisi#
Domain-specific error class'ları oluştur:
class BusinessLogicError extends Error {
statusCode: number
constructor(message: string, statusCode = 400) {
super(message)
this.statusCode = statusCode
this.name = 'BusinessLogicError'
}
}
// Handler'larda kullan
if (!isValidBusinessRule(data)) {
throw new BusinessLogicError('Invalid business data', 422)
}
4. Security Header'ları Standart Olmalı#
Security header'larını atlama. İşte benim standart konfigürasyonum:
.use(httpSecurityHeaders({
contentTypeOptions: 'nosniff',
frameOptions: 'DENY',
contentSecurityPolicy: "default-src 'self'",
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}))
5. Configuration Data'yı Cache'le#
Sık çağrılan fonksiyonlar için pahalı configuration'ları cache'le:
.use(ssm({
cache: true,
cacheExpiry: 5 * 60 * 1000, // 5 dakika
names: {
dbConfig: '/myapp/database/config',
apiKeys: '/myapp/external/api-keys'
}
}))
Middy Fonksiyonlarını Test Etme#
Middy'nin en büyük avantajlarından biri testability'yi nasıl geliştirdiği. Business logic'i middleware'den ayrı olarak test edebiliyorsun:
// Saf business logic'i test et
describe('User Registration Logic', () => {
test('should create new user with valid data', async () => {
const mockEvent = {
body: {
email: 'test@example.com',
password: 'SecurePass123!',
firstName: 'John',
lastName: 'Doe'
}
} as APIGatewayProxyEvent
// Core handler'ı doğrudan test et
const result = await registerUser(mockEvent, {} as any)
expect(result.statusCode).toBe(201)
const responseBody = JSON.parse(result.body)
expect(responseBody.email).toBe('test@example.com')
expect(responseBody.password).toBeUndefined()
})
})
// Tam middleware chain'ini test et
describe('User Registration API', () => {
test('should handle invalid JSON', async () => {
const event = {
body: 'invalid json',
headers: { 'content-type': 'application/json' }
} as any
const result = await handler(event, {} as any)
expect(result.statusCode).toBe(400)
})
test('should validate required fields', async () => {
const event = {
body: JSON.stringify({
email: 'test@example.com'
// Gerekli alanlar eksik
}),
headers: { 'content-type': 'application/json' }
} as any
const result = await handler(event, {} as any)
expect(result.statusCode).toBe(400)
})
})
Middy'yi NE ZAMAN Kullanmamak Gerekir#
Middy her zaman doğru seçim değil. Şu durumlarda kullanma:
- Ultra düşük gecikme fonksiyonları her milisaniyenin önemli olduğu yerlerde
- Tek amaçlı utility'ler minimal logic ile
- Memory kısıtlı ortamlar bundle size'ın kritik olduğu yerlerde
- Framework-agnostic kütüphaneler explicit composition'un tercih edildiği yerlerde
Kaçınılması Gereken Yaygın Tuzaklar#
Deneyimimizden, bu sorunlara dikkat et:
- Basit fonksiyonları over-engineer etme - Her Lambda middleware'e ihtiyaç duymaz
- Middleware sıralamasını göz ardı etme - Parse before validate, validate before business logic
- Cold start'larda heavy middleware'ler - Initialization overhead'ına dikkat et
- Sensitive data loglama - Input/output logging middleware'i ile dikkatli ol
- Configuration cache'lememe - External data için built-in caching kullan
Başlangıç#
Middy'yi denemek için hazır mısın? İşte starter kit'in:
# Core paket
npm install @middy/core
# Temel middleware'ler
npm install @middy/http-json-body-parser @middy/http-error-handler @middy/validator
# Güvenlik ve CORS
npm install @middy/http-cors @middy/http-security-headers
# Performance yardımcıları
npm install @middy/do-not-wait-for-empty-event-loop @middy/warmup
# AWS servis entegrasyonları
npm install @middy/ssm @middy/secrets-manager
Basit bir HTTP API ile başla, middleware'leri kademeli olarak ekle ve Lambda fonksiyonlarının daha sürdürülebilir ve tutarlı hale geldiğini izle.
Sırada Ne Var?#
Middy çoğu kullanım durumu için mükemmel, ama daha fazlasına ihtiyacın olduğunda ne olur? 2. Bölüm'de, production'da karşılaştığımız sınırları ve karmaşık business requirement'ları handle etmek ve performance'ı optimize etmek için nasıl kendi custom middleware framework'ümüzü oluşturduğumuzu keşfedeceksin.
Öğreneceğin konular:
- Scale'de keşfettiğimiz performance darboğazları
- Multi-tenant uygulamalar için dynamic middleware oluşturma
- Custom framework design pattern'leri
- Middy'den custom çözümlere migration stratejileri
- Gerçek performance benchmark'ları ve trade-off'lar
Middy, Lambda fonksiyonlarını yazma şeklimizi dönüştürdü, onları daha temiz, test edilebilir ve sürdürülebilir hale getirdi. Bu pattern'leri öğren, ilk günden itibaren daha iyi serverless kod yazacaksın.
AWS Lambda Middleware Uzmanlığı
Middy temellerinden production ölçeği Lambda uygulamaları için özel middleware framework'leri oluşturmaya
Bu Serideki Tüm Yazılar
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!
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!