TypeScript Geliştiricilerin Monolitten Lambda'ya Taşıdığı Beş Anti-Pattern
DI container'lar, monolitik SDK'lar, god-handler'lar, modül üstü secret çağrıları ve ağır ORM'ler - soğuk başlatmada bedeli ve yerine geçen fonksiyonel yapı.
Problem
Ekipler NestJS, Spring veya .NET monolitlerinden AWS Lambda'ya geçerken uzun ömürlü servislerde işe yarayan kalıpları yanlarında getiriyor. Bundle'lar şişiyor, cold start uzuyor ve platformun ekonomisi geri tepiyor. Lambda bir mikroservis değil, fonksiyondur - monolit OO/DI kalıpları bundle'ı şişirir, cold start'ı uzatır.
Hasarın çoğunu beş alışkanlık üretiyor: DI container'lar, modüler olmayan AWS SDK import'ları, god-handler servis sınıfları, modül üstünde senkron secret çağrısı ve ağır ORM'ler. Aşağıdaki her bölüm semptomu adlandırır, maliyeti açıklar ve fonksiyonel alternatifi verir.
En belirgin gerilim dependency injection. NestJS'in serverless dokümantasyonu @nestjs/platform-fastify ile serverless-http kombinasyonunu desteklenen bir kalıp olarak sunar. AWS Lambda Best Practices ise deployment paketini şişiren framework'lerden kaçınılmasını ve handler'ların ince tutulmasını önerir. Her iki kaynak da yetkili; ancak maliyet asimetrisi tartışmalı değil. Uzun ömürlü bir serviste DI container milyonlarca isteğe yayılarak kendini amorti eder. Lambda'da ise her cold container kurulum maliyetini sıfırdan öder.
DI Container'lar (NestJS, tsyringe, InversifyJS)
Nasıl Görünür
Handler @Injectable ile dekore edilmiş, modül repository'leri, servisleri ve provider'ları bağlıyor, reflect-metadata her giriş noktasının üstünden import ediliyor. Handler, business logic'in ilk satırından önce controller'ı container üzerinden çözer.
Bedeli
reflect-metadata her cold start'a yüklenir. Decorator metadata'sı startup'ta üretilir. Container, handler tek bir bağımlılık kullansa bile tüm grafı baştan çözer. Bundle boyutu modüldeki her provider'ın maliyeti kadar büyür, yalnızca bu handler'ın kullandığı kadar değil. esbuild, container'ın çağıracağı constructor'ları tree-shake edemez.
Fonksiyonel Alternatif
Düz bir async fonksiyon export edin. Bağımlılıkları modül kapsamlı bir değişken içinde lazy şekilde kurun, ilk çağrıda başlatın, sonraki sıcak çağrılarda yeniden kullanın.
??= operatörü ilk çağrıda başlatır ve singleton'ı sıcak çağrılar arasında yeniden kullanır. Testler bağımlılıkları fonksiyon argümanı olarak geçer; bu da bir container'ı mock'lamaktan daha basittir.
Modüler Olmayan AWS SDK Import'ları
Nasıl Görünür
import * as AWS from 'aws-sdk' - Eylül 2024'te maintenance mode'a giren ve 2025-09-08'de end-of-support'a ulaşan v2 monolitik SDK. Güncel Node.js Lambda runtime'ları yerine @aws-sdk/* v3 paketlerini içerir.
Bedeli
v2 SDK her AWS servisini kapsayan tek büyük bir bundle. esbuild bir kısmını tree-shake edebilir ancak public yüzey ve paylaşılan iç yapı taban maliyeti yüksek tutar. v3 ise servis başına paketlere bölünmüştür ve middleware ayrı import edilir. Fark hem bundle boyutunda hem de INIT duration'da ölçülür.
Fonksiyonel Alternatif
Yalnızca kullandığınız client ve command'ları import edin. @aws-sdk/client-* paketlerinde kalın. esbuild ile bundle'layın, runtime'da gelen SDK'yi external olarak işaretleyin, gerisini tree-shaking yapsın.
Build komutu runtime tarafından sağlanan SDK'yi external olarak işaretler; böylece bundle'da tekrar etmez:
Her runtime sürümünde @aws-sdk/* paketlerinin runtime'a dahil olmaya devam ettiğini doğrulayın; AWS, runtime başına dahil edilen sürümleri yayımlar.
God-Handler Servis Sınıfları
Nasıl Görünür
Handler OrderService'e devreder; o OrderRepository'yi kullanır; o da Logger, MetricsClient ve DatabasePool'a bağımlıdır. Beşi de baştan kurulur çünkü monolit böyle yapar. Handler dosyası tek bir sınıf import eder ve üzerinde tek bir method çağırır.
Bedeli
Graftaki her bağımlılık cold start'ta constructor'ını çalıştırır. Handler erken çıksa bile bellek bunları fonksiyon ömrü boyunca tutar. Tree-shaking, runtime'ın çağıracağı constructor'ları eleyemez. Daha kötüsü, bu sınıflardan biri ağır bir transitif bağımlılık çekerse bundle bunu da miras alır.
Fonksiyonel Alternatif
Bir handler tek iş yapar. Çapraz kesişen ihtiyaçlar - logging, tracing, validation - Lambda Powertools veya Middy üzerinden middleware'e taşınır. Paylaşılan mantık, container'ın gezeceği bir grafa asılı sınıflara değil, handler başına import edilen saf fonksiyonlara taşınır.
Middy middleware'i düz fonksiyonlar olarak komposlar; Powertools utility'leri isimle import edildiğinde tree-shake edilebilir. Her iki yol da handler'ı küçük tutar.
Modül Üstünde Senkron Secret/SSM Çağrısı
Nasıl Görünür
Bedeli
Top-level await, INIT fazında çalışır ve INIT cold start'ın parçasıdır. Her cold container, handler başlamadan önce SSM veya Secrets Manager'a gidiş-dönüş bedelini öder. Ağ yavaşsa veya parameter store rate-limit'liyse billed duration büyür. INIT hataları, handler hataları gibi aynı şekilde yeniden denenmez.
Fonksiyonel Alternatif
İlk çağrıda lazy başlatın. Sonucu modül kapsamlı bir değişkende cache'leyin. Sıcak yollar için AWS Parameters and Secrets Lambda Extension, localhost HTTP endpoint arkasında TTL'li in-memory cache sağlar.
İlk çağrı gidiş-dönüşü öder; sıcak çağrılar atlar. Parameters and Secrets extension layer'ı eklendiğinde lookup yerel cache'e düşer ve gidiş-dönüş handler'ınızdan tamamen çıkar.
Ağır ORM'ler (Prisma, TypeORM, Mongoose)
Nasıl Görünür
Prisma client modül üstünden import edilmiş. prisma generate build'e dahil edilmiş. Query engine binary'si deployment paketine düşmüş. Metadata reflection ile TypeORM ise DI container ile aynı şekle sahiptir - startup'ta eager graf inşası. Mongoose ise modül yüklemesinde mongoose.connect(...) ve mongoose.model('Order', schema) çağrılarıyla bundle'daki her şemayı handler ihtiyaç duysun duymasın derler.
Bedeli
Prisma'nın query engine'i, client'ın bağlandığı ayrı bir binary. Bundle boyutu, runtime'ın @aws-sdk/* external numarasının çözemeyeceği megabyte'larla büyür. Cold start uzar çünkü engine ilk sorgudan önce başlatılır. TypeORM'in reflection adımı, DI container'larda reflect-metadata'nın ettiği gibi INIT'i bloklar. Mongoose, MongoDB driver'ını ve BSON parser'ını model kayıt defteriyle birlikte paketler; şema derleme ve plugin kaydı her cold start'ta modül yüklenirken çalışır, modül üstündeki mongoose.connect() ise dördüncü anti-pattern'in uyardığı INIT-fazı bloklamasını ekler.
Fonksiyonel Alternatif
DynamoDB şekilli iş için v3 SDK'nin DynamoDBDocumentClient yeterli. SQL için Kysely gibi ince bir query builder küçük gelir ve lazy yüklenir. MongoDB için resmi mongodb driver'ı tek başına birkaç yüz kilobyte; Mongoose'un sağladığı şema katmanı yerine Zod gibi küçük bir validator ile eşleyin ve bağlantıyı modül üstünde değil, modül kapsamlı bir değişken içinde lazy başlatın. Tam ORM ve ODM'leri, engine maliyetinin amorti olabileceği uzun ömürlü servislere saklayın.
Her fonksiyon bir ila üç tabloya dokunduğunda elle yazılmış SQL veya küçük bir builder kabul edilebilir. Yüzey bunun ötesine geçtiğinde, tasarımın verdiği sinyal şu sorudur: bu iş gerçekten Lambda'ya mı ait?
Default Ne Zaman Geçerli, Ne Zaman Override Edilir
Dallar override durumlarını adlandırır. Middy veya Powertools default'a eklenen bir şeydir, default'un yerine geçen değil. Aynı kaynak üzerinde iki üç sıkı ilişkili işlem yapan bir handler küçük bir router'ı paylaşabilir; erken bölmek başka yerde cold-start yüzeyi yaratır. Bu default'a uymayan override, paylaşılan validation'lı küçük bir CRUD seti - lambdalith ile Powertools orada makul. Default ise hâlâ ince fonksiyon.
Nasıl Ölçülür
İki sayı ince yapının çalışıp çalışmadığını söyler: esbuild sonrası bundle boyutu ve CloudWatch'tan INIT duration.
Bundle boyutu esbuild çıktı satırında görünür. Build'i çalıştırın ve byte sayısını okuyun:
Tek amaçlı bir handler için 1 MB altını hedefleyin. Tree-shake edilmiş bir v3 client artı tipik business logic bunun çok altına iner.
INIT duration, CloudWatch'un her cold çağrı sonunda bastığı REPORT satırında çıkar:
Node.js için 200 ms altını hedefleyin. p99 cold start, Lambda Insights ve X-Ray'de ayrı raporlanır; sıcak p99'dan ayrı bir percentile olarak takip edin. Bağımsız çalışmalar (Lumigo, Datadog, AWS Compute Blog) Node.js cold start'ında bundle boyutunun ve modül üstü işin baskın iki etken olduğunu, bu sırada, tutarlı şekilde gösterir.
Kapanış
Lambda inceyi ödüllendirir. Her handler'ı lazy bağımlılıkları ve modüler import'ları olan tek tipli bir fonksiyon olarak ele alın. Çapraz kesişen ihtiyaçlar geldiğinde middleware'e (Powertools, Middy) uzanın; SQL kaçınılmaz olduğunda bir query builder'a uzanın. DI container'ları ve ağır ORM'leri, tasarlandıkları uzun ömürlü servislere saklayın.
Sınırı adlandırmak gerekirse: bu öneri route başına fonksiyon default'una uyar. Powertools'lu küçük bir CRUD lambdalith makul bir override; tam bir domain modeli ve repository grafı isteyen bir servis ise Lambda'yı değil ECS veya App Runner'ı istediğinizin sinyalidir.
Kaynaklar
- AWS Lambda Execution Environment - INIT fazı, container yaşam döngüsü ve modül üstü kodun neden cold-start maliyeti olduğunu açıklayan model
- AWS Lambda Best Practices - İnce handler, lazy başlatma ve modüler SDK kullanımı için kanonik AWS rehberi
- AWS SDK for JavaScript v3 Modüler Paketler - Servis başına
@aws-sdk/client-*paketlerinin bundle'ı neden küçülttüğü - AWS SDK for JavaScript v2 Maintenance Mode Duyurusu - v2'den geçişin resmi gerekçesi
- AWS Lambda Node.js Runtime'ları - Şu anda desteklenen Node.js runtime sürümleri ve runtime'a dahil SDK sürümleri
- esbuild Tree Shaking - Ölü kod elemenin nasıl çalıştığı ve neyin onu bozduğu
- AWS Lambda Powertools for TypeScript - Logging, tracing, metrics ve validation için modüler middleware
- Middy.js - Lambda handler'ları için hafif middleware framework'ü
- AWS Parameters and Secrets Lambda Extension - SSM ve Secrets Manager değerleri için in-memory cache
- NestJS Serverless Rehberi - DI gerilimine karşı tarafın görünümü; karşıtlığı görmek için yararlı
- Prisma Serverless Rehberi - Cold-start maliyetine açık notlar içeren satıcı rehberi
- Mongoose AWS Lambda Rehberi - Mongoose'un kendi rehberi: bağlantı cache'leme ve
mongoose.connect()cold-start tuzağı - AWS Compute Blog - Lambda'da Node.js Optimizasyonu - Ölçülmüş sayılarla bundle boyutu ve cold-start rehberi
- Lumigo - Lambda Cold Start Çalışması - Runtime'lar, boyutlar ve bağımlılıklar arasında bağımsız ölçümler