Serverless + esbuild + Prisma Paketleme Kabusumu Sonunda Nasıl Çözdüm
Serverless mimariler, geliştiricilere hız, ölçeklenebilirlik ve maliyet etkinliği sunarken, bazı özel zorlukları da beraberinde getirir. Özellikle esbuild gibi modern bundler’lar ve Prisma gibi güçlü ORM araçları bir araya geldiğinde, paketleme ve dağıtım süreçleri tam bir kabusa dönüşebilir. Bu makalede, bu üçlünün yarattığı paketleme sorunlarını, nedenlerini ve sonunda bu karmaşık düğümü nasıl çözdüğümü adım adım anlatacağım. Amacım, benim yaşadığım zorlukları sizin yaşamamanız için kapsamlı bir rehber sunmak.
Kabusun Başlangıcı: Neden Bu Kadar Zor?
Serverless, esbuild ve Prisma üçlüsünün bir araya gelmesi, başlangıçta kulağa harika bir kombinasyon gibi gelir. esbuild’in inanılmaz hızı, Serverless’ın esnekliği ve Prisma’nın tip güvenli veritabanı erişimi… Ancak bu potansiyel, genellikle paketleme ve dağıtım aşamasında beklenmedik engellerle karşılaşır. Peki, bu zorluklar nereden kaynaklanıyor?
Serverless Ortamının Kısıtlamaları
AWS Lambda gibi sunucusuz ortamlar, fonksiyonlarınızı çalıştırmak için belirli kısıtlamalar getirir. En belirgin olanları, paket boyutu limitleri (sıkıştırılmış 50MB, sıkıştırılmamış 250MB) ve çalışma zamanı ortamının (runtime environment) belirli bir yapıya sahip olmasıdır. Fonksiyonunuzun ihtiyaç duyduğu tüm bağımlılıkların bu sınırlar içinde olması ve doğru yollardan erişilebilir olması gerekir. Özellikle yerel bağımlılıklar (native dependencies) veya çalışma zamanında dinamik olarak yüklenen modüller bu noktada sorun yaratır.
esbuild’in Hızının Bedeli: Paketleme Zorlukları
esbuild, JavaScript/TypeScript projelerini inanılmaz bir hızla paketleyen, Go ile yazılmış bir bundler’dır. Bu hızı, genellikle her şeyi tek bir dosyaya (veya az sayıda dosyaya) derleyerek ve gereksiz kodları eleyerek elde eder. Ancak bu agresif optimizasyon yaklaşımı, özellikle çalışma zamanında dinamik olarak yüklenen dosyalar veya platforma özel ikili dosyalar (binaries) söz konusu olduğunda sorunlara yol açar. esbuild, varsayılan olarak bu tür dosyaları bundle’a dahil etmez çünkü onları “kullanılmayan kod” olarak algılayabilir.
Prisma’nın Yapısı ve Bağımlılıkları
Prisma, diğer ORM’lerden farklı olarak, veritabanı etkileşimleri için bir query-engine kullanır. Bu motor, projenizin node_modules/@prisma/client dizininde, kullandığınız platforma (örn. linux-arm64-openssl-3.0.x) özel olarak derlenmiş ikili bir dosya olarak bulunur. Prisma client’ı, çalışma zamanında bu motoru dinamik olarak yüklemeye çalışır. Serverless ortamında, bu dinamik yükleme mekanizmasının doğru yolu bulması ve motorun paketlenmiş Lambda fonksiyonunuz içinde mevcut olması kritik öneme sahiptir. Ayrıca, Prisma’nın node_modules içindeki diğer yardımcı dosyaları ve şema dosyası (schema.prisma) da doğru şekilde paketlenmelidir.
Geleneksel Yaklaşımlar ve Neden Başarısız Oldular
Bu sorunlarla ilk karşılaştığımda, doğal olarak bilinen veya ilk akla gelen çözümleri denedim. Ancak her biri, Serverless + esbuild + Prisma kombinasyonunun kendine özgü zorlukları karşısında yetersiz kaldı.
serverless-plugin-include-dependencies Denemeleri
serverless-plugin-include-dependencies eklentisi, Serverless projelerinde node_modules klasörünü paketlemeye yardımcı olan popüler bir araçtır. İlk başta bu eklentinin tüm sorunumu çözeceğini düşündüm. serverless.yml dosyama ekledim ve umutla deploy ettim. Ancak, esbuild’in zaten kendi bağımlılık yönetimi olduğu ve Prisma’nın özel ikili dosyalarıyla bu eklentinin tam olarak nasıl etkileşime girdiği belirsizdi. Sonuç olarak, fonksiyonlarım ya çok büyük oldu ya da Prisma motorunu bulamadığı için çalışma zamanında hata verdi. esbuild’in external bağımlılıkları ve serverless-esbuild eklentisinin kendi paketleme mantığıyla çakıştığı durumlar da oldu.
npm prune ve yarn install --production Çözümleri
Paket boyutunu küçültmek için genellikle npm prune --production veya yarn install --production gibi komutlar kullanılır. Bu komutlar, sadece üretim bağımlılıklarını tutarak geliştirme bağımlılıklarını siler. Bu yaklaşım, genel node_modules boyutunu azaltmada etkili olsa da, Prisma’nın çalışma zamanında ihtiyaç duyduğu platforma özel query-engine ikili dosyasını veya diğer yardımcı dosyalarını yanlışlıkla silme riski taşır. Ayrıca, esbuild’in zaten bundle’ladığı bağımlılıklarla çakışarak gereksiz iş yükü yaratabilir.
Manuel Kopyalama ve node_modules Yönetimi
Çaresizlik içinde, Prisma’nın ihtiyaç duyduğu dosyaları manuel olarak kopyalamayı veya belirli node_modules alt klasörlerini doğrudan serverless.yml‘deki package.include direktifleriyle dahil etmeyi denedim. Örneğin:
package:
include:
- node_modules/@prisma/client/**
- node_modules/.prisma/client/**
- schema.prisma
Bu yöntem, belirli bir dereceye kadar işe yarasa da, oldukça kırılgandı. Prisma sürüm güncellemelerinde veya farklı platformlarda (yerel geliştirme vs. Lambda) farklılıklar olduğunda sorunlar ortaya çıktı. Ayrıca, bu manuel yaklaşım, esbuild'in optimizasyon yeteneklerini tam olarak kullanamadığı için paket boyutunu gereğinden fazla büyütebiliyordu. Her bağımlılığı tek tek takip etmek, büyük projelerde sürdürülemez bir çözümdü.
Kilit Nokta: Prisma'nın Çalışma Şeklini Anlamak
Bu kabustan kurtulmanın anahtarı, Prisma'nın arka planda nasıl çalıştığını ve hangi dosyaların ne zaman ve nerede gerekli olduğunu tam olarak anlamaktan geçiyordu. Bu bilgi, doğru paketleme stratejisini geliştirmemde kritik rol oynadı.
prisma generate ve node_modules/@prisma/client
prisma generate komutu, schema.prisma dosyanızı kullanarak tip güvenli Prisma client'ını oluşturur. Bu client, projenizin node_modules/@prisma/client dizinine yerleşir. Bu dizin sadece JavaScript/TypeScript dosyalarını değil, aynı zamanda query-engine ikili dosyasını ve diğer platforma özel varlıkları da içerir. Özellikle node_modules/@prisma/client/runtime klasörü ve node_modules/@prisma/client/query-engine-* gibi dosyalar önemlidir.
query-engine ve Platform Bağımlılıkları
Prisma'nın kalbinde, veritabanı sorgularını yürüten query-engine bulunur. Bu motor, çalıştığı ortama göre farklılık gösteren ikili bir dosyadır. Örneğin, AWS Lambda'nın standart çalışma zamanı genellikle linux-x64 veya linux-arm64 mimarisine sahiptir. prisma generate komutu, varsayılan olarak projenizin package.json dosyasındaki prisma.engines veya prisma.binaryTargets yapılandırmasına göre uygun motoru indirir. Eğer bu yapılandırma yapılmazsa, genellikle yerel geliştirme ortamınızın platformuna uygun olanı indirir. Bu, Lambda'ya deploy ettiğinizde motorun bulunamamasına neden olabilir.
Dinamik Yükleme ve Yol Sorunları
Prisma client'ı, çalışma zamanında query-engine ikili dosyasını dinamik olarak yükler. Bunu yaparken, genellikle node_modules/@prisma/client içindeki belirli yollara bakar. Serverless ortamında, dosya sistemi yapısı ve erişim yolları yerel geliştirme ortamınızdan farklı olabilir. esbuild, bu dinamik yüklemeyi statik bir bağımlılık olarak görmediği için, query-engine'i bundle'a dahil etmeyebilir. Sonuç olarak, Lambda fonksiyonunuz çalışırken motoru bulamaz ve bir hata fırlatır. Bu, genellikle "Cannot find module '.../query-engine-...' " veya "Error: ENOENT: no such file or directory" gibi hatalarla kendini gösterir.
Nihai Çözüme Doğru: Adım Adım Yaklaşım
Tüm bu denemeler ve Prisma'nın çalışma şeklini anladıktan sonra, sonunda Serverless + esbuild + Prisma paketleme kabusumu sona erdirecek sağlam bir çözüm geliştirdim. Bu çözüm, serverless-esbuild eklentisinin güçlü özelliklerini, Prisma'nın ihtiyaçlarını ve Lambda'nın kısıtlamalarını bir araya getiriyor.
esbuild Konfigürasyonunu Optimize Etmek
serverless-esbuild eklentisi, serverless.yml dosyanızda esbuild yapılandırmasını yönetmenizi sağlar. Burada dikkat etmemiz gereken birkaç önemli nokta var:
* external Bağımlılıklar: esbuild'in node_modules içindeki bazı bağımlılıkları bundle'a dahil etmesini istemeyebiliriz, çünkü bunlar zaten Lambda ortamında mevcut olabilir veya manuel olarak dahil edeceğimiz bağımlılıklar olabilir. Prisma için bu genellikle geçerli değildir, ancak diğer bazı yerel bağımlılıklar için kullanılabilir.
* platform: 'node': Bu ayar, esbuild'e kodun Node.js ortamında çalışacağını bildirir ve buna göre optimizasyonlar yapar.
* target: Lambda'nın Node.js versiyonuna uygun bir hedef belirlemek önemlidir (örn. node18).
# serverless.yml içinde
custom:
esbuild:
bundle: true
minify: true
sourcemap: true
platform: 'node'
target: 'node18' # Lambda çalışma zamanına göre ayarlayın
# Diğer esbuild ayarları...
Prisma Bağımlılıklarını Doğru Paketlemek
Asıl sihirli kısım burası. serverless-esbuild eklentisi, package.patterns özelliği sayesinde esbuild'in bundle'lamadığı ancak dağıtım paketine dahil edilmesi gereken dosyaları belirtmemize olanak tanır.
1. Prisma Client ve Schema: node_modules/@prisma/client ve schema.prisma dosyaları mutlaka dahil edilmeli.
2. query-engine: En kritik parça olan query-engine ikili dosyası, doğru platform için seçilerek dahil edilmelidir. binaryTargets ayarını kullanarak Lambda'nın mimarisine uygun motoru indirdiğinizden emin olun.
Örnek serverless.yml yapılandırması:
# serverless.yml
service: my-prisma-app
plugins:
- serverless-esbuild
provider:
name: aws
runtime: nodejs18.x
region: eu-central-1
# ... diğer ayarlar
custom:
esbuild:
bundle: true
minify: true
sourcemap: true
platform: 'node'
target: 'node18'
# Prisma'nın external bağımlılıklarını belirterek esbuild'in onları bundle'lamasını engelliyoruz
# Bu, Prisma'nın kendi dinamik yükleme mekanizmasını kullanmasını sağlar
external:
- '@prisma/client' # Prisma client'ı kendisi
- '@prisma/engines' # Prisma motorları
package:
patterns:
# Prisma client'ın kendisi ve runtime dosyaları
- '!node_modules/@prisma/client/node_modules/**' # İç içe bağımlılıkları hariç tut
- 'node_modules/@prisma/client/**'
# .prisma klasörünü dahil et (query-engine burada olabilir)
- 'node_modules/.prisma/**'
# schema.prisma dosyasını dahil et
- 'schema.prisma'
# Eğer özel bir engines klasörü kullanıyorsanız
- 'node_modules/@prisma/engines/**'
# Sadece gerekli query-engine'i dahil etmek için daha spesifik bir yaklaşım:
# 'node_modules/.prisma/client/query-engine-linux-arm64-openssl-3.0.x'
# 'node_modules/.prisma/client/libquery_engine-linux-arm64-openssl-3.0.x.so.node'
# veya eğer linux-x64 kullanıyorsanız:
# 'node_modules/.prisma/client/query-engine-linux-x64-openssl-3.0.x'
# 'node_modules/.prisma/client/libquery_engine-linux-x64-openssl-3.0.x.so.node'
# Not: Yukarıdaki pattern'ler, node_modules/.prisma/** ile zaten kapsanmış olabilir.
# Önemli olan, doğru platforma ait motorun dahil edildiğinden emin olmaktır.
# Eğer sadece tek bir motor kullanıyorsanız, package.json'daki binaryTargets ayarını kontrol edin.
package.json dosyanızda prisma.binaryTargets'ı Lambda'nızın çalışacağı ortama göre ayarladığınızdan emin olun:
// package.json
{
"name": "my-serverless-app",
"version": "1.0.0",
"prisma": {
"seed": "ts-node prisma/seed.ts",
"binaryTargets": ["linux-arm64-openssl-3.0.x"] // Veya "linux-x64-openssl-3.0.x"
},
"scripts": {
"prisma:generate": "prisma generate",
"deploy": "npm run prisma:generate && sls deploy"
},
"dependencies": {
"@prisma/client": "^5.x.x",
"prisma": "^5.x.x"
}
}
Önemli Not: prisma.binaryTargets içindeki openssl versiyonu, kullandığınız Node.js runtime'ına ve AWS Lambda ortamına göre değişebilir. Örneğin, Node.js 18 için genellikle openssl-3.0.x kullanılırken, daha eski versiyonlar için openssl-1.1.x gerekebilir. Doğru versiyonu kullandığınızdan emin olun.
Lambda Katmanları (Layers) ile Daha Temiz Bir Yapı
Paket boyutunu daha da optimize etmek ve dağıtım süresini kısaltmak için Prisma motorunu ve hatta tüm @prisma/client bağımlılığını bir Lambda Katmanına taşıyabilirsiniz. Bu, fonksiyon kodunuzu motor bağımlılığından ayırır ve motorun her fonksiyon dağıtımında yeniden yüklenmesini engeller.
Avantajları:
* Fonksiyon paket boyutu küçülür.
* Dağıtım süresi kısalır.
* Birden fazla fonksiyon aynı Prisma motorunu kullanabilir.
Dezavantajları:
* Katman yönetimi ek karmaşıklık getirir.
* Prisma sürüm güncellemelerinde katmanın da güncellenmesi gerekir.
Uygulama:
1. Prisma motorunu ve node_modules/@prisma/client klasörünü içeren ayrı bir zip dosyası oluşturun.
2. Bu zip dosyasını bir Lambda katmanı olarak AWS'ye yükleyin.
3. serverless.yml dosyanızda fonksiyonlarınıza bu katmanı ekleyin ve package.patterns içinden Prisma ile ilgili tüm bağımlılıkları çıkarın.
# serverless.yml (Lambda Katmanı ile)
service: my-prisma-app-with-layer
plugins:
- serverless-esbuild
- serverless-layers # Eğer katmanı Serverless ile yönetiyorsanız
provider:
name: aws
runtime: nodejs18.x
region: eu-central-1
layers:
- arn:aws:lambda:eu-central-1:123456789012:layer:PrismaLayer:1 # Kendi katman ARN'nizi buraya yazın
custom:
esbuild:
bundle: true
minify: true
sourcemap: true
platform: 'node'
target: 'node18'
# Prisma'yı artık katmanda olduğu için external olarak işaretleyebiliriz
external:
- '@prisma/client'
- '@prisma/engines'
package:
patterns:
# Prisma bağımlılıklarını artık dahil etmiyoruz
- 'schema.prisma' # Schema dosyası hala gerekli olabilir
Bu yaklaşım, özellikle büyük projelerde veya birden fazla Lambda fonksiyonunun aynı Prisma bağımlılığını kullandığı durumlarda çok daha temiz ve verimli bir çözüm sunar.
Örnek Bir serverless.yml ve esbuild Yapılandırması
İşte tüm bu çözümleri bir araya getiren örnek bir serverless.yml ve ilgili package.json scriptleri:
serverless.yml Yapılandırması
service: my-serverless-prisma-app
frameworkVersion: '3'
plugins:
- serverless-esbuild
provider:
name: aws
runtime: nodejs18.x
region: eu-central-1
memorySize: 256 # Minimum Prisma için 256MB önerilir
timeout: 30 # Gerekirse artırılabilir
environment:
DATABASE_URL: ${env:DATABASE_URL} # Ortam değişkeni olarak veritabanı URL'si
functions:
hello:
handler: src/functions/hello.handler
events:
- httpApi:
path: /hello
method: get
users:
handler: src/functions/users.handler
events:
- httpApi:
path: /users
method: get
custom:
esbuild:
bundle: true
minify: true
sourcemap: true
platform: 'node'
target: 'node18'
# Prisma'yı external olarak işaretlemek, esbuild'in onu bundle'lamasını engeller.
# Bu sayede Prisma'nın kendi dinamik yükleme mekanizması devreye girer.
external:
- '@prisma/client'
- '@prisma/engines' # Eğer engines klasörü ayrı bir bağımlılık olarak yönetiliyorsa
package:
# esbuild'in bundle'lamadığı ancak dağıtım paketine dahil edilmesi gereken dosyalar
patterns:
# Prisma client'ın kendisi ve runtime dosyaları
- 'node_modules/@prisma/client/**'
# .prisma klasörünü dahil et (query-engine ve diğer platforma özel dosyalar burada bulunur)
- 'node_modules/.prisma/**'
# schema.prisma dosyasını dahil et, çünkü Prisma çalışma zamanında buna ihtiyaç duyabilir
- 'schema.prisma'
# node_modules içindeki bazı gereksiz dosyaları hariç tutarak paket boyutunu optimize et
- '!node_modules/**/README.md'
- '!node_modules/**/*.map'
- '!node_modules/**/*.txt'
- '!node_modules/**/*.yml'
- '!node_modules/**/*.d.ts'
- '!node_modules/**/package.json' # package.json'ları hariç tutmak bazen sorun yaratabilir, dikkatli olun
- '!node_modules/@types/**'
- '!node_modules/aws-sdk/**' # Eğer Lambda ortamında zaten mevcutsa
package.json Scriptleri
{
"name": "my-serverless-prisma-app",
"version": "1.0.0",
"description": "Serverless application with esbuild and Prisma",
"main": "handler.js",
"scripts": {
"prisma:generate": "prisma generate",
"deploy": "npm run prisma:generate && sls deploy",
"dev": "sls offline start",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@prisma/client": "^5.x.x"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.134",
"@types/node": "^20.11.24",
"esbuild": "^0.20.1",
"prisma": "^5.x.x",
"serverless": "^3.38.0",
"serverless-esbuild": "^1.50.0",
"serverless-offline": "^13.3.3",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"prisma": {
"seed": "ts-node prisma/seed.ts",
"binaryTargets": ["linux-arm64-openssl-3.0.x"] # Veya "linux-x64-openssl-3.0.x"
}
}
Bu yapılandırma ile, npm run deploy komutunu çalıştırdığınızda:
1. prisma generate çalışır ve @prisma/client ile query-engine'i (belirtilen binaryTargets'a göre) oluşturur.
2. serverless-esbuild devreye girer, fonksiyon kodunuzu esbuild ile bundle'lar.
3. package.patterns sayesinde, esbuild'in bundle'lamadığı ancak Serverless'ın dağıtım paketine dahil etmesi gereken Prisma ile ilgili tüm dosyalar (client, motor, schema) doğru yerlerine kopyalanır.
Bu sayede, Lambda fonksiyonunuz Prisma'yı sorunsuz bir şekilde bulup kullanabilir.
Sonuç: Uzun Soluklu Bir Mücadelenin Sonu
Serverless, esbuild ve Prisma'yı bir araya getirme yolculuğu, beklentimin çok ötesinde bir paketleme kabusuna dönüşmüştü. Saatler süren denemeler, onlarca hata mesajı ve derinlemesine araştırma sonucunda, bu üç teknolojinin inceliklerini anlamak ve doğru yapılandırmayı bulmak mümkün oldu. Kilit nokta, esbuild'in "ne yaptığını" ve Prisma'nın "neye ihtiyaç duyduğunu" tam olarak kavramak ve serverless-esbuild eklentisinin package.patterns gücünü doğru kullanmaktı.
Artık projelerimi bu güçlü kombinasyonla gönül rahatlığıyla geliştirip dağıtabiliyorum. Umarım bu detaylı rehber, sizin de benzer sorunlarla boğuşurken zaman ve sinir kaybı yaşamanızı engeller. Unutmayın, her teknoloji yığını kendi özel zorluklarını beraberinde getirir, ancak doğru bilgi ve yaklaşımla her sorunun bir çözümü vardır.
SSS (Sıkça Sorulan Sorular)
Neden sadece serverless-plugin-include-dependencies kullanmadık?
serverless-plugin-include-dependencies genellikle tüm node_modules klasörünü dahil etmeye çalışır. esbuild ile birlikte kullanıldığında, esbuild zaten çoğu bağımlılığı bundle'ladığı için bu gereksiz olur ve paket boyutunu şişirir. Ayrıca, Prisma'nın dinamik olarak yüklediği query-engine gibi özel ikili dosyaları her zaman doğru şekilde ele almayabilir. serverless-esbuild'in package.patterns özelliği, esbuild'in çıktısına eklenmesi gereken spesifik dosyaları daha hassas bir şekilde kontrol etmemizi sağlar.
Lambda katmanları her zaman gerekli mi?
Hayır, her zaman gerekli değildir. Eğer fonksiyonunuzun paket boyutu AWS Lambda'nın sıkıştırılmamış 250MB (veya sıkıştırılmış 50MB) limitini aşmıyorsa ve dağıtım süresi sizin için kabul edilebilir seviyelerdeyse, katman kullanmak zorunda değilsiniz. Ancak, özellikle birden fazla fonksiyonunuz varsa ve hepsi aynı Prisma motorunu kullanıyorsa, katmanlar paket boyutunu küçültmek ve dağıtım hızını artırmak için harika bir yoldur.
Bu çözüm diğer ORM'ler için de geçerli mi?
Temel paketleme prensipleri (esbuild'in bağımlılıkları nasıl ele aldığı, Serverless'ın dosya sistemi kısıtlamaları) diğer ORM'ler için de geçerli olabilir. Ancak, her ORM'in kendine özgü bağımlılık yapısı ve çalışma zamanı gereksinimleri vardır. Örneğin, TypeORM veya Sequelize gibi ORM'ler genellikle Prisma gibi ayrı bir ikili motor kullanmazlar. Bu nedenle, onların paketleme süreçleri genellikle daha basittir ve sadece node_modules içindeki JavaScript dosyalarının doğru şekilde bundle'lanmasını veya dahil edilmesini gerektirir. Bu makaledeki çözümler Prisma'ya özeldir, ancak genel yaklaşım (esbuild'in external ve serverless-esbuild'in package.patterns kullanımı) diğer karmaşık bağımlılıklar için bir başlangıç noktası olabilir.
yarn yerine npm kullanırsam ne değişir?
yarn veya npm kullanmanız, package.json içindeki bağımlılıkları yönetme şeklinizi değiştirir, ancak Prisma'nın kendisinin veya esbuild'in çalışma şeklini doğrudan etkilemez. prisma generate komutu ve Serverless dağıtım süreci her iki paket yöneticisiyle de sorunsuz çalışır. Önemli olan, prisma generate komutunun sls deploy öncesinde çalıştırıldığından ve package.json'daki binaryTargets ayarının doğru olduğundan emin olmaktır.
