2025-09-08
Monolit'in İntikamı: Microservisler Teknik Borç Haline Geldiğinde
Dağıtık monolitleri tanıma, stratejik servis konsolidasyonu ve microservis karmaşıklığı sürdürülemez hale geldiğinde modüler monolitlere geri dönüşün gerçekleri üzerine bir perspektif.
Microservis mimarisi dağıtık monolit haline geldiğinde ekipler, kaçınmaya çalıştıkları tam sorunlarla yüzleşir. Dağıtık monolit sendromu genellikle deployment coupling, veri tutarlılığı zorlukları ve koordinasyon yüküyle kendini gösterir. Konsolidasyon geçerli bir mimari pattern; başarısızlık itirafı değil.
47 Servisle Alışveriş Sepeti Kâbusu
Sadece sepete ürün eklemek için 47 microservise evrilmiş basit bir e-ticaret platformu. Her servisin kendi veritabanı, deployment pipeline’ı ve nöbet rotasyonu var. Tek bir satın alma işlemi 12 farklı takımın koordinasyonunu gerektiriyor.
Mimari diyagram sunumlarda etkileyici görünür. Gerçekte ise feature geliştirmekten çok servisler arası iletişimi debug etmek zaman alır. “Loosely coupled” servisler bağımsız deploy edilemez çünkü bir API’yi değiştirmek beş takımla koordinasyon demektir. Klasik dağıtık monolit sendromu.
Microservisler Saldırdığında: Tanıma Kalıpları
Microservislerin teknik borca dönüştüğüne dair tutarlı uyarı işaretleri:
Deployment Ölüm Dansı
# Deployment "orkestrasyon"unuz böyle görünüyor
deploy-order:
- auth-service # İlk deploy edilmeli
- user-service # Auth değişikliklerine bağımlı
- profile-service # Yeni user alanlarına ihtiyaç duyar
- order-service # Profile güncellemelerini gerektirir
- inventory-service # Order değişikliklerine ihtiyaç duyar
- payment-service # Yukarıdakilerin hepsine bağımlı
# ... Belirli sırada 41 servis daha
Servisleri bağımsız deploy edemiyorsan, microservisin yok - ekstra adımları olan dağıtık bir monolitin var.
Veri Tutarlılığı Kâbusu
// Temiz servis sınırları olarak başlayan...
class OrderService {
async createOrder(orderData: OrderRequest) {
// Dağıtık transaction cehennemine dönüştü
const user = await this.userService.getUser(orderData.userId);
const inventory = await this.inventoryService.checkStock(orderData.items);
const pricing = await this.pricingService.calculateTotal(orderData);
const payment = await this.paymentService.authorize(pricing.total);
// Şimdi ortada hiçbir şey başarısız olmaması için dua et
try {
const order = await this.saveOrder(orderData);
await this.inventoryService.reserve(orderData.items);
await this.paymentService.capture(payment.id);
// Bu başarısız olursa ne olur?
await this.emailService.sendConfirmation(order);
return order;
} catch (error) {
// Tüm bunları tutarlı şekilde geri almak için bol şans
await this.attemptDistributedRollback(error);
}
}
}
Doğal sonuç: microservisleri koordine eden bir dağıtık transaction koordinatörü; yani aslında bir monolit. Mimarinin ironi anlayışı var.
Büyük Konsolidasyon Stratejisi
23 servisi 3 modüler monolite indiren bir konsolidasyon aşağıdaki karar framework’üyle yönlendirilir:
Servis Konsolidasyon Karar Matrisi
interface ConsolidationCandidate {
services: string[];
criteria: {
sharedDataModel: boolean; // Aynı kavramsal veri mi?
teamOwnership: string; // Aynı takım mı sahip?
deploymentCoupling: number; // Ne sıklıkla birlikte deploy ediliyor?
communicationVolume: number; // Aralarında dakikada kaç çağrı?
transactionBoundary: boolean; // ACID garantileri gerekli mi?
};
consolidationScore(): number {
// Skor > 0.7 ise, güçlü konsolidasyon adayı
return (
(this.criteria.sharedDataModel ? 0.3 : 0) +
(this.criteria.teamOwnership ? 0.2 : 0) +
(this.criteria.deploymentCoupling > 0.8 ? 0.2 : 0) +
(this.criteria.communicationVolume > 100 ? 0.2 : 0) +
(this.criteria.transactionBoundary ? 0.3 : 0)
);
}
}
Gerçekten İşe Yarayan Modüler Monolit Pattern’i
47 servis yerine, net iç sınırlara sahip 3 modüler monolit:
// Tek deployable, birden fazla modül
class ECommerceApplication {
// Net sınırlara sahip modüller
private modules = {
user: new UserModule(this.sharedDb),
order: new OrderModule(this.sharedDb),
inventory: new InventoryModule(this.sharedDb),
payment: new PaymentModule(this.sharedDb)
};
// Paylaşılan altyapı - sihirli sos
private sharedDb = new DatabaseConnection();
private cache = new RedisCache();
async processOrder(request: OrderRequest) {
// Dağıtık saga'lar yerine güzel ACID transaction'ları
return await this.sharedDb.transaction(async (tx) => {
const user = await this.modules.user.validateUser(request.userId, tx);
const items = await this.modules.inventory.reserveItems(request.items, tx);
const payment = await this.modules.payment.processPayment(request.payment, tx);
const order = await this.modules.order.createOrder(user, items, payment, tx);
// Her şey birlikte commit olur veya rollback yapar
return order;
});
}
}
Deployment süresi 45 dakikalık orkestrasyon kaosundan 4 dakikalık basit blue-green deployment’a düştü. Incident oranımız %60 azaldı. Takım hızı %40 arttı. Modüler monolitler, net iç sınırlarla microservis faydalarını karmaşıklık olmadan sunabilir; paylaşılan veritabanı ve ACID transaction’lar çoğu senaryoda eventual consistency’den daha basit.
Gerçek Konsolidasyon Hikayeleri: İyi, Kötü, Pahalı
Hızlı Büyüyen Startup Hesaplaşması
Hızla ölçeklenen bir fintech startup, 18 ay içinde 47 microservise ulaştı. Her yeni feature yeni bir servis demekti çünkü “Netflix böyle yapıyor.” Ama bu startup Netflix değil; 3.000 değil, 30 mühendisi var.
Durumun ciddiyeti bir yönetim kurulu demosunda netleşti. Basit bir kullanıcı kaydı 8 servis üzerinden çağrı tetikliyor. Payment servisi timeout aldığında, tüm akış başarısız oldu ve sistemde yarı oluşturulmuş bir kullanıcı kaldı.
Sonraki çeyrekte 4 domain odaklı servise konsolidasyon:
- Identity Service: Kullanıcı, auth, profiller, izinler
- Transaction Service: Ödemeler, siparişler, faturalama, mutabakat
- Product Service: Katalog, fiyatlama, envanter, öneriler
- Communication Service: Email, SMS, push bildirimleri, webhook’lar
Sonuç: feature teslimatı 3x iyileşti ve bug’lar dağıtık sistem arkeolojisi yapmadan takip edilebildi.
Tam Tur Atan Enterprise Migration
Bir Fortune 500 şirketi, 3 yılda legacy monolitten 200+ microservise geçti. Mimari o kadar karmaşıktı ki, neyin neyle konuştuğunu belgelemek için özel bir “Servis Haritacılığı Takımı” oluşturuldu.
Kritik bir denetim sırasında, tek bir uyumluluk raporunun 73 farklı servisten veri gerektirdiğini keşfettiler. Rapor 6 saat sürüyordu ve timeout cascade’leri nedeniyle %30 oranında başarısız oluyordu.
Konsolidasyon stratejisi cerrahi oldu:
-- Ayrı veritabanları yerine domain şemaları oluşturduk
CREATE SCHEMA customer_domain;
CREATE SCHEMA product_domain;
CREATE SCHEMA order_domain;
CREATE SCHEMA compliance_domain;
-- İlgili tabloları domain şemalarına taşıdık
ALTER TABLE users SET SCHEMA customer_domain;
ALTER TABLE profiles SET SCHEMA customer_domain;
ALTER TABLE preferences SET SCHEMA customer_domain;
-- Artık uyumluluk raporları basit join'ler
SELECT
c.user_id,
c.registration_date,
o.total_orders,
o.total_revenue,
p.product_categories
FROM customer_domain.users c
JOIN order_domain.order_summary o ON c.user_id = o.user_id
JOIN product_domain.user_products p ON c.user_id = p.user_id
WHERE c.registration_date >= '2024-01-01';
-- 6 saat değil, 3 saniyede çalışıyor
200+ servisten business domain’e göre organize edilmiş 12 modüler monolite geçtiler. Uyumluluk raporu üretimi 6 saatlik dağıtık sistem macerasından 3 saniyelik bir query’ye dönüştü.
Ölçeklenemeyen Performans Kritik Sistem
Bir gerçek zamanlı trading platformu, “sonsuz ölçeklenebilirlik” için sistemini microservislere ayırdı. Sorun: servisler arası network latency her trade execution’a 50-100ms ekliyordu. Yüksek frekanslı trading’de bu bir sonsuzluk.
Çözüm mantığa aykırı: her şeyi tek, yüksek optimize edilmiş bir process’e konsolide etmek.
// Önce: Network overhead'li microservisler
class TradingSystemDistributed {
async executeTrade(order: Order) {
// Her çağrı 10-20ms latency ekliyor
const validation = await this.validationService.validate(order); // +15ms
const pricing = await this.pricingService.getPrice(order); // +12ms
const risk = await this.riskService.checkLimits(order); // +18ms
const execution = await this.executionService.execute(order); // +14ms
const settlement = await this.settlementService.settle(order); // +16ms
// Toplam: Ortalama 75ms latency
}
}
// Sonra: Shared memory'li monolit
class TradingSystemMonolithic {
async executeTrade(order: Order) {
// Her şey in-process ve shared memory ile
const validation = this.validateOrder(order); // <1ms
const pricing = this.calculatePrice(order); // <1ms
const risk = this.checkRiskLimits(order); // <1ms
const execution = this.executeOrder(order); // <1ms
const settlement = this.settleOrder(order); // <1ms
// Toplam: <5ms latency
}
}
Trade execution 15x iyileşti. Bazen eski yöntemler en iyi yöntemlerdir.
Gerçekten İşe Yarayan Migration Stratejileri
Strangler Fig Pattern (Tersten)
Monoliti microservislerle boğmak yerine, bu pattern microservisleri monolitle boğar:
class ConsolidationProxy {
private legacyServices = new Map<string, MicroserviceClient>();
private consolidatedHandlers = new Map<string, Handler>();
async handleRequest(request: Request): Promise<Response> {
const feature = this.extractFeature(request);
// Trafiği yavaş yavaş konsolide versiyona taşı
if (this.shouldUseConsolidated(feature)) {
return await this.consolidatedHandlers.get(feature)!(request);
}
// Legacy microservice'e geri dön
return await this.legacyServices.get(feature)!.call(request);
}
private shouldUseConsolidated(feature: string): boolean {
// %10 trafikle başla, yavaş yavaş artır
const rolloutPercentage = this.getRolloutPercentage(feature);
return Math.random() < rolloutPercentage;
}
}
Her seferinde bir business capability migrate edilerek hata oranları ve performans her adımda izlenir. 6 aylık bir zaman çizelgesiyle sıfır büyük incident mümkündür.
Gözyaşsız Veritabanı Konsolidasyonu
Konsolidasyonun en korkutucu kısmı genellikle veritabanlarını birleştirmektir. İşe yarayan pattern:
-- Adım 1: Konsolide veritabanında domain şemaları oluştur
CREATE SCHEMA user_domain;
CREATE SCHEMA order_domain;
CREATE SCHEMA inventory_domain;
-- Adım 2: Microservis DB'lerinden logical replication kur
CREATE PUBLICATION user_pub FOR ALL TABLES;
CREATE SUBSCRIPTION user_sub
CONNECTION 'host=user-service-db dbname=users'
PUBLICATION user_pub;
-- Adım 3: Read'leri yavaş yavaş konsolide DB'ye taşı
-- Adım 4: Write'ları feature flag'lerle değiştir
-- Adım 5: Eski veritabanlarını devre dışı bırak
Kilit içgörü: bunu özel bir microservis sihri değil, herhangi bir veri migration’ı gibi ele al.
Kimsenin Konuşmadığı Maliyet Analizi
Konsolidasyonun altyapı ötesinde anlamlı maliyet tasarrufu sağladığı görülür:
Konsolidasyon Öncesi (47 Microservis): AWS $12,000/ay, DataDog $3,000/ay, PagerDuty $500/ay, developer overhead ~$45,000/ay. Toplam: $60,500/ay.
Konsolidasyon Sonrası (3 Modüler Monolit): AWS $4,000/ay, DataDog $800/ay, PagerDuty $100/ay, developer overhead ~$15,000/ay. Toplam: $19,900/ay. Yıllık tasarruf: $487,200.
Doğrudan maliyet tasarrufunun ötesinde, mühendislerin üzerinde çalıştıkları sistemi gerçekten anlayabildiklerinde çalışan memnuniyeti dramatik biçimde iyileşir.
Takım Dinamikleri ve Conway Yasasının İntikamı
Mimarlık derslerinde öğretmedikleri bir şey var: takım yapınız nihayetinde mimarinizi belirleyecek, tersi değil.
Konsolidasyonu Zorlayan Takım Reorganizasyonu
Bir organizasyon 12 küçük takımdan 4 büyük ürün takımına yeniden yapılandığında, 47 microservisi sürdürmek imkansız hale gelir. Her takım 10-12 servise sahip olacaktır. Conway Yasası ile savaşmak yerine, kucaklamak gerekir:
// Takım yapısı mimariyi yönlendirdi
interface TeamArchitectureAlignment {
teamStructure: {
identityTeam: 8, // 8 mühendis
commerceTeam: 10, // 10 mühendis
fulfillmentTeam: 6, // 6 mühendis
platformTeam: 6 // 6 mühendis
};
serviceStructure: {
identityService: 'identityTeam', // Takım başına 1 servis
commerceService: 'commerceTeam', // Net sahiplik
fulfillmentService: 'fulfillmentTeam',// Koordinasyon gerekmiyor
platformService: 'platformTeam' // Paylaşılan altyapı
};
}
Her takım bir modüler monolit’e sahipti. Nöbet yönetilebilir hale geldi. Bilgi paylaşımı iyileşti. Code review’ları mantıklı gelmeye başladı çünkü reviewer’lar bağlamı anlıyordu. Conway Yasası mimari kararları yönlendirir; takım sınırları servis sınırlarından önce gelmeli. Organizasyonel yapı değiştiğinde mimarinin de adapte olması gerekir.
Zamanın Testine Dayanan Modül Sınırları
Başarılı modüler monolitlerin sırrı modül sınırlarını doğru belirlemektir:
// Dependency injection ile net modül arayüzleri
@Module({
imports: [], // Döngüsel bağımlılık yok!
providers: [
OrderService,
OrderRepository,
OrderValidator,
OrderEventPublisher
],
exports: [OrderService] // Sadece servisi expose et
})
export class OrderModule {
// İç sınıflar modül-private
private repository: OrderRepository;
private validator: OrderValidator;
private events: OrderEventPublisher;
// Public arayüz minimal ve stabil
public service: OrderService;
}
// Build time'da sınırları zorla
class OrderService {
constructor(
// Sadece izin verilen modüllerden inject edilebilir
@Inject(UserModule) private users: UserService,
@Inject(InventoryModule) private inventory: InventoryService,
// @Inject(RandomModule) <- Bu build time'da başarısız olur
) {}
}
Anahtar: yanlış bağımlılıkları code review’da cesaretini kırmak değil, compile time’da imkansız hale getirmek. Modül sınırları servis sınırlarından daha önemli; önce iç sınırları doğru belirle.
Basitleştirilmiş Monitoring ve Observability
Konsolidasyonun beklenmedik bir faydası: monitoring gerçekten kullanışlı hale geldi.
Önce: Dağıtık Tracing Kâbusu
// Tek bir kullanıcı isteğini 12 servis üzerinden takip etmek
{
traceId: "abc-123",
spans: [
{ service: "api-gateway", duration: 5 },
{ service: "auth-service", duration: 45 },
{ service: "user-service", duration: 23 },
{ service: "profile-service", duration: 67 },
{ service: "preference-service", duration: 12 },
{ service: "recommendation-service", duration: 234 },
{ service: "content-service", duration: 56 },
{ service: "cache-service", duration: 3 },
{ service: "analytics-service", duration: 89 },
{ service: "notification-service", duration: 34 },
{ service: "email-service", duration: 156 },
{ service: "audit-service", duration: 45 }
],
totalDuration: 769,
status: "failed",
error: "recommendation-service'de 234ms sonra timeout"
}
Root cause’u bulmak 12 farklı servisten log’ları correlate etmeyi gerektiriyordu, her birinin kendi log formatı ve timestamp hassasiyeti vardı.
Sonra: Uygulama Seviyesi Observability
// Aynı istek modüler monolitte
{
requestId: "xyz-789",
module_timings: {
"auth.validateToken": 8,
"user.loadProfile": 15,
"recommendations.generate": 45,
"content.fetch": 12
},
totalDuration: 80,
databaseQueries: 4,
cacheHits: 12,
status: "success"
}
Tek log stream. Tek deployment. İşler ters gittiğinde bakacak tek yer. Devrim niteliğinde.
Karar Framework’ü
Birden fazla konsolidasyon döngüsünden çıkan gözlemler, aşağıdaki framework’ü destekler:
class ConsolidationDecisionFramework {
shouldConsolidate(): boolean {
const factors = {
// Teknik faktörler
deploymentCoupling: this.measureDeploymentCoupling(), // > 0.7 = konsolide et
sharedDataRequirements: this.assessDataSharing(), // > 0.6 = konsolide et
networkChattiness: this.measureServiceCommunication(), // > 100 çağrı/dk = konsolide et
transactionRequirements: this.needsAcidTransactions(), // true = kesinlikle düşün
// Organizasyonel faktörler
teamSize: this.getEngineeringHeadcount(), // < 50 = monolite yönel
teamStructure: this.assessTeamBoundaries(), // uyumsuz = konsolide et
onCallBurden: this.measureOnCallLoad(), // > 40saat/ay = konsolide et
// İş faktörleri
developmentVelocity: this.measureFeatureDelivery(), // azalıyor = uyarı işareti
operationalCost: this.calculateMonthlyBurn(), // sürdürülemez = konsolide et
timeToMarket: this.measureFeatureLeadTime(), // artıyor = problem
};
// Faktörlerin yarısından fazlası konsolidasyon öneriyorsa, yap
return this.calculateConsolidationScore(factors) > 0.5;
}
}
Geçmişe Bakış
Birden fazla microservis yolculuğunun dersleri:
Modüler Monolitle Başla
İyi yapılandırılmış bir modüler monolitten başlamak çoğu durumda tercih edilir. Servis çıkarma yalnızca şu durumlarda gereklidir:
- Bir modülün bağımsız ölçeklenmesi gerekiyor (spekülasyon değil, metriklerle kanıtlanmış)
- Bir modül farklı teknoloji gerektiriyor (meşru teknik gereksinim)
- Bir modül bağımsız deployment gerektiriyor (farklı release cycle’ları nedeniyle)
- Ayrı bir takım tamamen sahip olacak (Conway Yasası uyumluluğu)
Sadece Performansı Değil, Karmaşıklığı Ölç
Response time’ları ve throughput ölçmek yaygındır. Daha değerli metrikler:
- Bir sorunu debug etme süresi (alert’ten çözüme)
- Bir feature’ı anlamak için gereken kişi sayısı
- Developer başına bilişsel yük (günlük context switch’leri)
- Koordinasyona vs. yaratıma harcanan zaman
İlk Günden Konsolidasyon İçin Tasarla
Servisleri daha sonra birleştirebileceğin varsayımıyla geliştir:
- Uyumlu teknoloji stack’leri kullan
- Tutarlı veri modelleri koru
- API pattern’lerini standartlaştır
- Servis sınırlarının ve neden var olduklarının iyi belgelerini tut
Mimari Evrim Hakkında Dürüst Gerçek
Örüntü şudur: mükemmel mimari yok, sadece mevcut bağlamınıza uyan mimariler var. Microservisler kötü değil. Monolitler kötü değil. Microservis gibi davranan dağıtık monolitler - işte onlar kötü.
Monolitten microservislere, oradan modüler monolite sarkaç salınımı başarısızlık değil - öğrenme. Her mimari kararı gelecekteki gereksinimler, takım yapısı ve iş ihtiyaçları üzerine bir bahis. Bazen bu bahsi kaybedersin. Anahtar, kaybettiğini tanımak ve rotayı değiştirme cesaretine sahip olmak.
Modüler monolitler milyonlarca kullanıcıya başarıyla hizmet verebilir. Microservislere bölmek yalnızca karmaşıklık maliyetini haklı çıkaran zorlayıcı bir neden olduğunda tercih edilmelidir.
Temel Çıkarımlar
Teknik Liderler İçin:
- Servis konsolidasyonu geçerli bir mimari pattern, başarısızlık itirafı değil
- Takım bilişsel yükünü sistem metriklerini izlediğin kadar yakından izle
- Takım sınırlarının servis sınırlarını yönlendirmesine izin ver, tersi değil
- Operasyonel karmaşıklığın gerçek maliyetleri var - mimari kararlara dahil et
Development Takımları İçin:
- Modüler monolitler karmaşıklık olmadan microservis faydaları sağlayabilir
- Paylaşılan veritabanları ve ACID transaction’ları genellikle eventual consistency’den daha basit
- Monolitin içindeki modül sınırlarına odaklan - servis sınırlarından daha önemli
- Mutluluğun ve üretkenliğin geçerli mimari gereksinimler
Mimarlar İçin:
- Konsolidasyon olasılığı dahil, değişim için tasarla
- Sadece altyapıyı değil, mimarinin toplam maliyetini ölç
- Transaction sınırları genellikle servis sınırlarından daha önemli
- Bazen en iyi hamle geriye doğru - ve bu sorun değil
Kaynaklar
- MonolithFirst - Martin Fowler - Yeni projelere mikroservisler değil monolit olarak başlanması gerektiğine dair Martin Fowler’ın temel argümanı ve erken ayrıştırmanın neden ters teptiği
- Mikroservisler - Martin Fowler - Mikroservis mimari stilini, ödünleşimlerini ve ne zaman uygun olduğunu tanımlayan temel makale
- Mikroservis Ödünleşimleri - Martin Fowler - MicroservicePremium kavramı dahil mikroservislerin gerçek maliyetleri ve faydalarının analizi
- Strangler Fig Uygulaması - Martin Fowler - Alt sistemleri servislerle kademeli olarak değiştirerek monolitten geçiş için kullanılan örüntü
- Mikroservisler Oluşturmak, 2. Baskı - Sam Newman - Ayrıştırma, iletişim ve organizasyonel örüntüleri kapsayan mikroservis tasarımına kapsamlı rehber
- Mikroservisler Örüntü Dili - microservices.io - Chris Richardson’ın saga, strangler fig ve servis ayrıştırma yaklaşımları dahil mikroservis örüntüleri kataloğu
Amaç mimari saflık değil, ekibin verimli biçimde değer sunmasını sağlayan sistemler geliştirmektir. Microservislerin teknik borca dönüştüğünü kabul etmek ve daha basit bir yapıya geri konsolide etmek geçerli bir mimari hamledir.
İlgili yazılar
Dayanıklı event-driven sistemler için multi-account AWS mimari pattern'lerini öğrenin. Hesap yapısı, EventBridge routing, servisler arası iletişim ve dağıtık sistemlerde operasyonel zorlukları keşfedin.
AWS Verified Permissions, SpiceDB, OpenFGA, Cerbos ve OPA dahil harici yetkilendirme platformlarının tarafsız değerlendirmesi. Mimari desenler, maliyet analizi ve mühendislik ekipleri için karar çerçevesi.
SpiceDB ve Auth0 FGA (OpenFGA) arasında detaylı bir teknik karşılaştırma -- şema tasarımı, tutarlılık modelleri, dağıtım ve ölçeklenebilirlik açısından farklı tercihler yapan iki Zanzibar tabanlı yetkilendirme sistemi.
In-memory uygulama cache'lerinden distributed Redis cluster'lara ve CDN edge caching'e kadar çok katmanlı caching stratejilerini uygulamaya yönelik kapsamlı bir rehber. Cache-aside ve write-through pattern'leri ne zaman kullanılır, ElastiCache ile MemoryDB arasında nasıl seçim yapılır ve production'da cache stampede nasıl önlenir öğrenin.
Transactional Outbox Pattern'in dağıtık sistemlerdeki dual-write problemini nasıl çözdüğünü, PostgreSQL, DynamoDB ve CDC araçlarıyla pratik implementasyonlarını öğren.