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.
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.
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.
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.
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.
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.
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.
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.



