MICROSERVICES SAGA VE OUTBOX

Sipariş servisinde ödeme alındı, ama stok servisi düşemedi — şimdi ne olacak? Monolitte tek bir BEGIN TRANSACTION ile çözdüğünüz şey, microservices dünyasında bambaşka bir denkleme dönüşür. Her servisin kendi veritabanı varsa, klasik ACID transaction artık masada değildir. İşte tam bu noktada iki kalıp devreye girer: dağıtık iş akışını yönetmek için Saga, mesajlaşma tutarlılığını korumak için Outbox. Bu yazıda Choreography saga ile Orchestration saga arasındaki gerilimi ve dual-write probleminin neden bu kadar sinsi olduğunu somut örneklerle göreceksiniz.
1. Dağıtık İşlem Sorunu: Neden ACID Artık Yetmiyor?
Monolit bir uygulamada sipariş oluşturmak, ödeme almak ve stok düşmek tek bir veritabanı işleminde olur. Hata varsa ROLLBACK, bitti. Microservices'te ise her servis kendi veritabanına sahiptir; "Order", "Payment" ve "Inventory" servisleri farklı şemalar, hatta farklı veritabanı motorları kullanabilir. Bu izolasyon ölçeklenebilirlik için altın değerinde, ama tutarlılık için baş ağrısı.
İki klasik tuzak vardır:
- Dağıtık transaction (2PC): XA protokolü ile servisleri tek bir transaction'a bağlamak. Performans cezası ağır, koordinatör tek başarısızlık noktası, modern bulut altyapılarında pratik değil.
- Naif HTTP zinciri: Servisler birbirini senkron çağırır. Üçüncü çağrı patladığında öncekileri geri almak için ne mekanizmanız ne de kaydınız vardır.
Saga pattern, bu çıkmazı uzun süreli iş akışını bir dizi yerel transaction'a bölerek aşar. Her adım kendi servisinde commit edilir; bir adım başarısız olursa öncekiler için compensating transaction (telafi işlemi) çalıştırılır. Kavramın tarihçesi ve farklı varyantları için konuya ilişkin kaynaklar derli toplu bir referans sunar.
2. Choreography Saga: Olayların Dansı
Choreography yaklaşımında merkezi bir yönetici yoktur. Her servis, ilgilendiği olayı (event) dinler ve kendi işini bitirince yeni bir olay yayınlar. Order servisi OrderCreated yayınlar, Payment servisi bunu duyup ödemeyi alır ve PaymentCompleted yayınlar, Inventory bunu duyup stok düşer.

Avantajları belirgindir:
- Servisler birbirinden gevşek bağlıdır; yeni bir servis eklemek mevcut akışı bozmaz.
- Tek bir single point of failure yoktur.
- Event-driven mimariyle doğal olarak hizalanır; Kafka veya RabbitMQ gibi broker'larla rahat çalışır.
Ancak akış karmaşıklaştıkça takip etmek zorlaşır. 7 servisin birbirini tetiklediği bir akışta "şu an siparişin nerede tıkandı?" sorusuna cevap vermek detaylı distributed tracing gerektirir. Döngüsel bağımlılık riski de vardır: A servisi B'yi tetikler, B yanlışlıkla A'yı tekrar tetikleyebilir.
3. Orchestration Saga: Merkezi Şef
Orchestration yaklaşımında bir orchestrator (genelde "Saga Manager" denilen bir bileşen) tüm akışı yönetir. Orchestrator sırayla komutlar gönderir: "Payment, ödemeyi al", cevabı bekler, sonra "Inventory, stoğu düş" der. Bir adım başarısız olursa, geriye doğru telafi komutları gönderir.
Choreography ile karşılaştırırsak:
- Görünürlük: Orchestrator akışın anlık durumunu bilir; debug çok daha kolaydır.
- Karmaşıklık konumu: Choreography'de karmaşıklık dağılır, orchestration'da merkezde toplanır. Akış 10+ adıma çıkarsa orchestrator tercih edilir.
- Bağlılık: Servisler orchestrator'a bağımlıdır; ama birbirlerinden habersizdir.
- Test edilebilirlik: Orchestrator'un durum makinesi (state machine) ayrı test edilebilir.
Pratikte iki yaklaşım birlikte kullanılabilir: kritik para akışları orchestration, periferik bildirimler choreography ile. Spring ekosisteminde Axon Framework, Camunda veya Temporal gibi araçlar orchestration için olgun seçenekler sunar. Java tabanlı microservices mimarisini derinlemesine ele aldığımız Java microservices eğitimi içeriğinden bu pattern'lerin Spring Boot uygulamasını öğrenebilirsiniz.
4. Dual-Write Problemi: Sessiz Bir Tutarsızlık Tuzağı
Saga akışında her servis "yerel veritabanına yaz + event yayınla" yapar. İşte tam bu iki adımın atomik olmaması, dual-write problemidir. Düşünün:
- Order servisi
orderstablosuna INSERT yapar. - Hemen ardından Kafka'ya
OrderCreatedmesajı gönderir. - 1. adım başarılı, 2. adım Kafka bağlantısı koptuğu için başarısız.
Sonuç: Veritabanında sipariş var, ama hiçbir servis bundan haberdar değil. Tersi de olabilir — mesaj gönderildi, sonra DB commit patladı; bu sefer fantom sipariş için ödeme alınır. @Transactional ile mesaj göndermek de çözüm değildir, çünkü mesaj broker'ı tipik olarak DB transaction'ına dahil edilemez.
5. Outbox Pattern: Atomikliği Kurtarmak
Outbox pattern, mesajı doğrudan broker'a göndermek yerine aynı veritabanı transaction'ında bir outbox tablosuna yazar. İş veri ve mesaj artık tek bir ACID transaction içinde commit edilir.

Sonra ayrı bir süreç (relay/dispatcher) outbox tablosunu okur ve mesajları broker'a iletir:
- Polling relay: Basit bir scheduler outbox'u tarar, gönderilmemiş kayıtları yayınlar, gönderim sonrası işaretler. Basit ama gecikme yaratabilir.
- Change Data Capture (CDC): Debezium gibi araçlar veritabanı transaction log'unu (PostgreSQL WAL, MySQL binlog) okuyup outbox değişikliklerini Kafka'ya akıtır. Düşük gecikme, ekstra altyapı.
İki kritik detay: mesajlar at-least-once teslim edilir, bu yüzden tüketici tarafında idempotency şarttır (örneğin event ID'yi takip etmek). Ayrıca outbox tablosu büyür; eski kayıtları periyodik temizleyen bir job gerekir.
6. Saga ve Outbox Birlikte: Tam Tutarlılık Resmi
İki pattern aynı problemin farklı katmanlarını çözer. Saga, iş mantığı düzeyinde eventual consistency yönetir; outbox, mesajlaşma katmanında at-least-once garantisini sağlar. Saga olmadan outbox sadece güvenilir mesaj gönderir ama telafi mekanizması yoktur. Outbox olmadan saga, ilk mesaj kaybında çuvallar.
Pratik bir kurgu şöyle olur:
- Her servis kendi outbox tablosunu tutar.
- İş transaction'ı + outbox kaydı aynı commit'te gider.
- CDC veya polling ile mesaj broker'a aktarılır.
- Karşı servisler idempotent consumer olarak mesajı işler.
- Saga akışı choreography (event reaktif) veya orchestration (komut tabanlı) ile sürdürülür.
7. Ne Zaman Hangi Kombinasyon?
Üç ipucu üzerinden karar verebilirsiniz:
- 3-4 servisli, basit akış: Choreography + outbox yeter. Az kod, az araç.
- Çok adımlı para/finans akışı: Orchestration + outbox. Görünürlük ve compliance için orchestrator'un durum makinesi paha biçilmezdir.
- Hibrit ihtiyaç: Kritik yol orchestration, çevresel akışlar choreography. Aynı outbox altyapısı ikisini de besler.

Dağıtık sistemlerde "her zaman tutarlı" yerine "sonunda tutarlı ama izlenebilir" olmayı hedeflersiniz. Saga ve outbox, bu hedefin iki temel taşıdır. Spring Boot, Kafka ve Debezium ile bu pattern'lerin uygulanışını adım adım deneyimlemek isterseniz Java microservices eğitimi içeriğinden yararlanabilirsiniz; teorinin koda dökülmesi pattern'leri kalıcı hale getirir.
Microservices yolculuğunda hata yapmamak değil, hatalardan tutarlılığı kaybetmeden çıkmak önemlidir. Saga akışınızı net çizin, outbox tablonuzu erken kurun, idempotency'yi tüketici tarafında baştan tasarlayın — gerisi mühendislik disiplinidir.
Sıkça Sorulan Sorular
Choreography saga ile orchestration saga arasında nasıl seçim yapılır?
Servis sayısı ve akış karmaşıklığına bakın. 3-4 servisli, doğrusal akışlarda choreography daha hafif ve gevşek bağlı çalışır. 7+ adımlı, finansal ya da compliance gerektiren akışlarda orchestration tercih edin; merkezi durum makinesi görünürlük ve debug kolaylığı sağlar. Hibrit kullanım da geçerli bir seçenektir.
Dual-write problemi tam olarak nedir ve neden kritiktir?
Bir servisin önce kendi veritabanına yazıp ardından mesaj broker'ına event göndermesi sırasında iki işlemin atomik olmaması durumudur. DB commit edilip mesaj gönderilemezse veya tersi olursa sistem genelinde sessiz tutarsızlıklar oluşur; fantom siparişler, kayıp ödemeler gibi tespit edilmesi zor hatalar ortaya çıkar.
Outbox pattern dual-write problemini nasıl çözer?
Mesajı broker'a göndermek yerine aynı veritabanı transaction'ı içinde bir outbox tablosuna yazar. İş verisi ve mesaj artık tek ACID commit'te birlikte kaydedildiği için atomiklik garanti edilir. Ayrı bir relay süreci sonra bu tablodan mesajları broker'a iletir; böylece iki sistem arasındaki tutarsızlık penceresi kapatılır.
Compensating transaction nedir ve klasik rollback'ten farkı nedir?
Compensating transaction, daha önce commit edilmiş bir yerel işlemi mantıksal olarak geri alan yeni bir işlemdir. Klasik rollback DB seviyesinde geri alır; compensation ise yeni bir kayıt yazar (örneğin iade işlemi). Audit trail korunur ve geri alma diğer servislere de event olarak yayılabilir.
Outbox relay için polling mi CDC mi tercih edilmeli?
Polling basittir, ek altyapı gerektirmez ama saniyeler düzeyinde gecikme yaratır; küçük-orta ölçekli sistemler için yeterli olabilir. CDC (örneğin Debezium) milisaniye gecikmeyle çalışır ve veritabanı log'unu doğrudan okur. Yüksek hacim ve düşük gecikme şartı varsa CDC, hızlı başlangıç hedefliyorsanız polling önerilir.
Idempotency neden outbox pattern ile birlikte zorunludur?
Outbox + broker kombinasyonu at-least-once teslim garantisi sunar, yani aynı mesaj birden fazla kez ulaşabilir. Tüketici aynı event'i iki kez işlerse çift kayıt, çift ödeme gibi yan etkiler oluşur. Event ID'yi takip eden bir tablo veya unique constraint ile her mesajın yalnızca bir kez işlenmesi sağlanmalıdır.
Saga pattern uygulanan bir sistemde ACID garantileri tamamen kaybedilir mi?
Hayır. Her saga adımı kendi servisinin yerel veritabanında ACID transaction olarak çalışır. Kaybedilen şey servisler arası global ACID'tir; onun yerine eventual consistency ve compensation mekanizmaları gelir. Yani lokalde tutarlılık tam, küresel seviyede ise belirli bir zaman penceresinde tutarlı bir model benimsenir.
Outbox tablosu zamanla büyürse performans sorunu olur mu?
Evet, temizlenmeyen outbox tabloları yavaşlama ve disk tüketimi yaratır. Çözüm olarak gönderim sonrası kayıtları arşivleyen veya N gün sonra silen periyodik bir job kurulmalıdır. Ayrıca status ve created_at sütunlarına indeks koymak, polling sorgularının ölçeklenmesine yardımcı olur.


