Node.js'te Monolitten Mikroservislere Geçiş Rehberi
Node.js monolitlerini event-driven serverless fonksiyonlara dönüştürme rehberi, gerçek migrasyon stratejileri, mimari kalıplar ve tam bir dönüşümden öğrenilen dersler.
Monolitlerin Sürdürülemezliği
Node.js monolitleri öngörülebilir biçimde bozulur: tek bir ortak deployment sınırı, yavaş bir domain’in tüm release döngüsünü bloke etmesi anlamına gelir ve yoğun trafik kaynaklı arızalar tüm servislere eş zamanlı yayılır. Mimari çözüm monoliti yeniden düzenlemek değil; onu bağımsız olarak deploy edilebilen, event-driven fonksiyonlara ayrıştırmaktır. O ayrıştırmayı taşıyan şey, migrasyon stratejisi, başarıyı belirleyen sınır kararları ve geçişi operasyonel açıdan güvenli kılan Node.js kalıplarıdır.
Bu deneyim bize asıl problemin sadece teknik borç değil, mimari olduğunu öğretti. Monolit, tek bir takımın etkili bir şekilde sürdürebileceği, debug edebileceği ve geliştirebileceği noktanın ötesine geçmişti.
Legacy MVC monolitten event-driven serverless fonksiyonlara geçiş, mimari kararlara, migrasyon stratejilerine ve dönüşüm boyunca öğrenilen derslere dayanır.
Monolit Zorluğu
E-ticaret platformu basit bir Node.js Express uygulaması olarak başlamıştı:
// Alçakgönüllü başlangıç - çok masum görünüyordu
app.use('/api/users', userController);
app.use('/api/products', productController);
app.use('/api/orders', orderController);
app.use('/api/inventory', inventoryController);
app.use('/api/payments', paymentController);
// ... 47 controller daha
Zamanla, bu uygulama karmaşık bir sisteme dönüştü:
- Büyük kod tabanı binlerce dosyaya yayılmış
- Birden fazla business domain tek bir repository’de
- Uzun deploy süreleri kapsamlı test suite’leri dahil
- Azalan takım hızı karmaşıklık arttıkça
- Yüksek altyapı maliyetleri monolitik deployment için
- Önemli debugging yükü geliştirme zamanını tüketiyor
Asıl Sorun: Teknik Borç Değil, Bilişsel Yük
Herkes monolitlerden bahsederken “teknik borç”tan söz eder, ama asıl katil bilişsel yüktü. Tipik bir “basit” feature şöyle görünüyordu:
// "Ürün önerisi" feature'ı eklemek için anlamam gerekenler:
// 1. User service (authentication + preferences)
class UserService {
async getUserPreferences(userId: string) {
// 347 satır business logic
// + 12 farklı database çağrısı
// + 4 external service entegrasyonu
}
}
// 2. Product service (catalog + inventory + pricing)
class ProductService {
async getRecommendations(userId: string, context: string) {
// 6 farklı öneri stratejisi boyunca 892 satır
// + ML model entegrasyonu
// + A/B test framework'ü
// + Cache invalidation logic (en zor kısım)
}
}
// 3. Order service (satın alma geçmişi analizi için)
class OrderService {
async getUserOrderHistory(userId: string, limit?: number) {
// 234 satır karmaşık SQL join
// + Veri gizliliği uyum logic'i
// + "VIP" kullanıcılar için performans optimizasyonları
}
}
// 4. Analytics service (önerileri takip etmek için)
class AnalyticsService {
async trackRecommendationEvent(event: RecommendationEvent) {
// 156 satır event işleme
// + GDPR uyumu
// + Rate limiting
// + Queue management
}
}
Tek bir öneri endpoint’i eklemek, birden fazla service boyunca geniş kodu, çok sayıda database tablosunu ve birkaç external API’yi anlamayı gerektiriyordu. Deneyimli mühendisler bile yeni feature’lar implement etmeden önce mevcut mimariyi anlamak için önemli zaman harcıyordu.
Kırılma Noktası: Feature Geliştirme Darboğazları
Bir dönüm noktası, görünüşte basit bir feature isteğinin problemin derinliğini ortaya çıkarmasıyla geldi: alışveriş sepetinde ilgili ürünleri göstermek.
Basit olması gereken şey uzun bir projeye dönüştü:
- Mevcut mimariyi anlama birden fazla birbirine bağlı service boyunca
- Dikkatli entegrasyon mevcut iş akışlarını bozmamak için
- Kapsamlı test karmaşık test suite’lerinde regresyonu önlemek için
- İteratif düzeltmeler değişiklikler sistemin beklenmedik kısımlarını etkilediğinde
- Cascade debugging bir düzeltme başka bir bileşeni bozduğunda
- Basitleştirilmiş uzlaşma tam implementasyon çok riskli olduğunda
Sonuç: Hızlı bir geliştirme olması gereken şey için haftalarca mühendislik çabası.
Hız metrikleri aşamalı bozulmayı ortaya çıkardı:
// Mühendislik hızı trendleri
const velocityData = {
'erken-günler': {
featuresPerMonth: 'yüksek',
avgDeployTime: 'dakikalar',
hotfixTime: 'hızlı',
teamMorale: 'pozitif'
},
'orta-dönem': {
featuresPerMonth: 'azalan',
avgDeployTime: 'uzun',
hotfixTime: 'saatler',
teamMorale: 'sinirli'
},
'kırılma-noktası': {
featuresPerMonth: 'minimal',
avgDeployTime: 'çok-uzun',
hotfixTime: 'çok-saatler',
teamMorale: 'düşük' // Takım devri arttı
}
};
Stratejik Karar: Evrimsel Mimari
Azalan verimlilik ve artan teknik zorluklarla karşı karşıya kalınca, üç seçeneği araştırdık: tamamen yeniden yazma, takımı önemli ölçüde büyütme veya stratejik mimari evrim. Üçüncü yolu seçtik: operasyonel gerçeklik tarafından yönlendirilen metodical ayrıştırma.
Teorik domain modellerini takip etmek yerine, operasyonel acının service sınırlarımızı yönlendirmesine izin verdik.
Acı-Odaklı Service Extraction
Service adaylarını deployment ve operasyonel kalıplara göre belirledik:
- Beraber değişen bileşenler sıkı coupling gösteriyordu
- Beraber başarısız olan bileşenler paylaşılan risk faktörlerini ortaya çıkarıyordu
- Farklı ölçekleme ihtiyaçları olan bileşenler extraction adaylarıydı
Birkaç ay boyunca deployment kalıplarımızın analizimiz:
// Deployment korelasyon analizi
const serviceAnalysis = {
'sıkı-bağlı': {
'user-auth': ['notification', 'profile'],
'product-catalog': ['inventory', 'pricing'],
'order-processing': ['payment', 'shipping']
},
'extraction-adayları': {
'analytics': 'farklı-ölçekleme-ihtiyaçları',
'admin-tools': 'farklı-release-cycle',
'recommendations': 'izole-hatalar'
}
};
Faz 1: Düşük-Risk Extractionlar (Aylar 1-3)
En güvenli extractionlarla başladık - şu özelliklere sahip bileşenler:
- Zaten izole minimal paylaşılan bağımlılıklar ile
- Yüksek-acı, düşük-risk analytics ve admin tools gibi
- Farklı operasyonel özellikler ML öneri motorları gibi
İlk sonuçlar:
- Hızlı deploymentlar çıkarılan serviceler için
- İzole analytics ana uygulamayı artık etkilemiyor
- Bağımsız admin geliştirme özel takım iş akışları ile
- Azaltılmış altyapı karmaşıklığı temel olmayan serviceler için
Faz 2: Temel Business Logic (Aylar 4-8)
İkinci faz gelir-kritik bileşenleri ele aldı: ürün yönetimi, sipariş işleme ve ödeme işleme.
Event-Driven Mimari Tanıtımı:
Anahtar atılım, service iletişimi için AWS EventBridge’i benimsemekti. Bu bizi synchronous HTTP çağrılarından asynchronous event-driven kalıplara kaydırdı:
// Önce: Synchronous coupling
async function processOrder(orderData) {
// Synchronous bağımlılıklar cascade failure riski yaratır
const user = await userService.validateUser(orderData.userId);
const inventory = await inventoryService.reserveItems(orderData.items);
const payment = await paymentService.processPayment(orderData.payment);
const shipping = await shippingService.calculateShipping(orderData.address);
// Tek bir transaction'da birden fazla başarısızlık noktası
return await orderService.createOrder({ user, inventory, payment, shipping });
}
// Sonra: Event-driven dayanıklılık
async function processOrder(orderData) {
// Sipariş kaydını hemen oluştur
const order = await orderService.createOrder(orderData);
// Downstream işleme için event publish et
await eventBridge.publish('order.created', {
orderId: order.id,
userId: orderData.userId,
items: orderData.items,
timestamp: Date.now()
});
// Azaltılmış coupling ve cascade failure riski
return order;
}
Faz 3: Serverless Fonksiyonlar (Aylar 9-12)
Net service sınırları oluşturulduktan sonra, serverless fonksiyonlara doğru evrimleştik:
Fonksiyon-Tabanlı Mimari:
Her service, odaklanmış, tek amaçlı fonksiyonların bir koleksiyonu haline geldi:
// product-service/functions/get-product.ts
export const handler = async (event: APIGatewayEvent) => {
const { productId } = event.pathParameters;
// Tek sorumluluk: Ürün verisini al
const product = await dynamodb.get({
TableName: 'Products',
Key: { id: productId }
}).promise();
return {
statusCode: 200,
body: JSON.stringify(product.Item)
};
};
// product-service/functions/inventory-updated.ts
export const handler = async (event: EventBridgeEvent) => {
const { productId, newQuantity } = event.detail;
// Tek sorumluluk: Envanter değişikliklerine tepki ver
await dynamodb.update({
TableName: 'Products',
Key: { id: productId },
UpdateExpression: 'SET inventory = :qty',
ExpressionAttributeValues: { ':qty': newQuantity }
}).promise();
// Gerekirse downstream event publish et
if (newQuantity === 0) {
await eventBridge.putEvents({
Entries: [{
Source: 'product-service',
DetailType: 'Product Out of Stock',
Detail: JSON.stringify({ productId })
}]
}).promise();
}
};
Dönüşüm Sonuçları
12 aylık metodical evrimden sonra, mimari dönüşüm ölçülebilir iyileştirmeler sağladı:
Performans İyileştirmeleri
| Metrik | Önce | Sonra | İyileştirme |
|---|---|---|---|
| Deploy Süresi | 45+ dakika | ~3 dakika | Önemli ölçüde hızlı |
| Hotfix Süresi | Birkaç saat | ~10 dakika | Çok daha hızlı yanıt |
| Feature Hızı | Azalan | Artan | Dikkat çeken iyileştirme |
| Yanıt Süresi | Değişken/yavaş | Tutarlı/hızlı | Daha iyi performans |
Maliyet Optimizasyonu
// Altyapı maliyet karşılaştırması
const costAnalysis = {
monolithArchitecture: {
infrastructure: 'yüksek-sabit-maliyetler', // Her zaman açık EC2 instance'ları
monitoring: 'karmaşık-araçlar', // Özel monitoring stack
deployment: 'ci-cd-yükü', // Build/deploy altyapısı
scaling: 'sadece-dikey'
},
serverlessArchitecture: {
infrastructure: 'kullandığın-kadar-öde', // Lambda + yönetilen serviceler
monitoring: 'native-aws-araçları', // Dahili CloudWatch
deployment: 'sıfır-altyapı', // Native AWS deployment
scaling: 'otomatik-yatay'
},
benefits: {
costReduction: 'önemli-tasarruflar',
operationalSimplicity: 'azaltılmış-bakım',
scalability: 'daha-iyi-trafik-işleme'
}
};
Developer Experience İyileştirmeleri
En önemli değişiklikler takım verimliliği ve memnuniyetindeydi:
- Hızlı onboarding: Yeni takım üyeleri çok daha hızlı katkı sağlayabiliyordu
- İzole debugging: Problemleri bulmak ve düzeltmek kolaylaştı
- Bağımsız geliştirme: Takımlar geniş koordinasyon olmadan feature’lar üzerinde çalışabiliyordu
- Azaltılmış incident’lar: Daha az production problemi ve daha hızlı çözüm
Temel Dersler
Bu mimari dönüşümü tamamladıktan sonra, birkaç anahtar içgörü ortaya çıktı:
1. Operasyonel Gerçeklik
Teorik domain modellerle başlamak yerine, gerçek operasyonel acı noktalarını analiz ederek başarı bulduk. Service sınırları, soyut business domain’lerden ziyade takım iş akışları ve deployment kalıpları ile daha iyi hizalandı.
2. Event-Driven İletişim ve Dayanıklılık
Event-driven mimari sadece gevşek coupling’den fazlasını sağladı - sistem dayanıklılığını temelden iyileştirdi. Serviceler eventler aracılığıyla asynchronous iletişim kurduğunda, hatalar cascade olmak yerine izole olma eğilimindedir.
3. Fonksiyonların Business Logic Uyumu
Birçok kullanım durumu için, tam microservice’lerin karmaşıklığı gerçek gereksinimleri aştı. Basit, odaklanmış fonksiyonlar daha uygun oldu, net sınırlar ve daha kolay debugging sundu.
4. Dahili Observability
Dağıtık fonksiyonlarla, kapsamlı monitoring ve tracing temel hale geldi:
// Dağıtık fonksiyonlar için temel observability
import { captureAWS, captureHTTPsGlobal } from 'aws-xray-sdk';
import AWS from 'aws-sdk';
// Dağıtık tracing'i etkinleştir
captureAWS(AWS);
captureHTTPsGlobal(require('https'));
export const handler = async (event, context) => {
// Otomatik tracing fonksiyon yürütmesine görünürlük sağlar
// ve downstream service çağrıları ile korele eder
};
Migrasyon Strateji Rehberi
Bu dönüşüm deneyimine dayanarak, işte pratik bir migrasyon yaklaşımı:
1. Değerlendirme Fazı
- Acı noktalarını belirle: Deployment başarısızlıklarını, debugging zamanını ve geliştirme darboğazlarını haritalandır
- Bağımlılıkları analiz et: Service etkileşimlerini ve paylaşılan kaynakları belgele
- Baseline ölç: Mevcut performans ve maliyet metriklerini oluştur
2. Extraction Stratejisi
- Çevreden başla: İzole, kritik olmayan bileşenlerle başla
- Operasyonel kalıpları takip et: Service sınırlarını yönlendirmek için deployment korelasyonunu kullan
- Veri tutarlılığını sürdür: Database ayrıştırmasını dikkatli planla
3. Event-Driven Geçiş
- Event bus tanıt: Basit pub/sub kalıpları ile başla
- Aşamalı decoupling: Synchronous çağrıları artımlı olarak değiştir
- Failure için tasarla: Event işlemeye dayanıklılık yap
4. Fonksiyon Evrimi
- Tek sorumluluk: Fonksiyonları odaklanmış ve stateless tut
- Event tetikleyiciler: Fonksiyonları belirli eventlere yanıt verecek şekilde tasarla
- Observability önce: Başlangıçtan kapsamlı monitoring implement et
Ortaya Çıkan Mimari İlkeler
Bu dönüşüm birkaç anahtar mimari ilkeyi pekiştirdi:
Basitlik Önceliği
En başarılı bileşenler genellikle en basit olanlardı. Karmaşık framework’ler ve kalıplar sıklıkla çözdüklerinden daha fazla problem yarattı.
Operasyonel Hizalama
Takım yapısı ve operasyonel süreçlerle hizalanan mimari, teorik olarak “mükemmel” tasarımlardan daha sürdürülebilir oldu.
Evrimsel Yaklaşım
Aşamalı evrim öğrenme ve kurs düzeltmesine izin verdi, big-bang yeniden yazmalardan daha başarılı oldu.
Event-First Tasarım
Event tasarımı ile başlamak daha iyi service sınırları ve daha dayanıklı sistemler oluşturmaya yardımcı oldu.
Sonraki Evrim: Pure Function Mimarisi
Bu monolit-microservice yolculuğu, geleneksel service kalıplarının çoğunun gereksiz karmaşıklık olduğunu ortaya çıkardı. Bir sonraki evrim, eventlere yanıt veren pure, stateless fonksiyonlara doğru hareket ediyor.
Gelecekteki keşif için anahtar alanlar:
- Node.js serverless ortamlarında fonksiyonel programlama kalıpları
- Minimal orkestrasyon ile event-driven sistem tasarımı
- Yüksek oranda dağıtık fonksiyon mimarileri için observability stratejileri
- Event-driven, fonksiyon-tabanlı sistemler için test yaklaşımları
Serverless paradigması altyapı değişikliklerinden fazlasını temsil ediyor - daha basit, daha odaklanmış mimari kalıplara doğru temel bir kayma.
Kaynaklar
- AWS Lambda nedir? - Monolit Express uygulamalarından olay güdümlü fonksiyonlara geçen ekipler için temel Lambda kavramları
- Amazon EventBridge nedir? - Ayrıştırılmış servislerin bağımsız iletişimini sağlayan event bus modeli
- AWS Ortamınızı Birden Fazla Hesapla Düzenleme - Mimari olgunlaştıkça servisleri hesaplar arasında izole etme rehberi
- Serverless Uygulamalar Lens - AWS Well-Architected - Monolitik iş yüklerini serverless servislere ayrıştırma kalıpları
- AWS Lambda fonksiyonları için en iyi uygulamalar - Stateless tasarım ve servis sınırları genelinde paylaşılan mutable state’den kaçınma
- AWS CDK v2 Geliştirici Rehberi - Çıkarılan microservislerin bağımsız dağıtımı için altyapı-kod yaklaşımı
İlgili yazılar
Node.js microservice'lerimizden factory, service ve dependency injection'ı çıkarmak kodun %40'ını ve bug'ların çoğunu sildi; fonksiyonlar sınıfları neden geçti?
CloudEvents spesifikasyonu ve TypeScript SDK'sı için pratik rehber: AWS Lambda ve EventBridge'de standart eventler oluştur, parse et ve doğrula.
Runtime seçimi, veritabanı ayarları, bundle boyutu azaltma ve caching ile AWS Lambda'da sub-10ms response süreleri elde edin. Gerçek benchmark'larla.
Event odaklı sistemlerin arkasındaki zihinsel değişimi öğrenin: komut vermek yerine olguları duyurmak. İsimlendirme, ayrıştırma, nihai tutarlılık ve idempotency.
Nub ve Vite+, 2026'nın oxc tabanlı iki Rust araç zinciri; rakip gibi görünüp öyle değiller. Hangi ikilinin hangi repoda yeri olduğuna dair net bir kural.