Fin ve Tech: Dağıtık Sistemlerde Saga Pattern — Kredi Kullandırım Süreciyle Anlatım

Bir kredi başvurusu onaylandı. Şimdi sırada kullandırım var: limit bloke edilecek, tutar müşterinin hesabına geçecek, hayat sigortası poliçesi kesilecek, muhasebeye işlenecek ve müşteriye bildirim gidecek.
Beş adım, beş farklı servis, beş farklı veritabanı. Dördüncü adımda sigorta şirketinin dış API’si 503 döndürdü. Tek veritabanı olsaydı cevap basitti: ROLLBACK. Dağıtık dünyada öyle bir komut yok — ve olmayacak.
Kısa cevap: Saga pattern nedir?
Saga, uzun bir işlemi her biri kendi başına kesinleşen küçük adımlara böler. Her adımın bir de telafisi vardır. Bir adım patladığında geriye doğru rollback yapılmaz; o ana kadar yapılanlar iş anlamında compensate edilir (telafi edilir). Amaç, tek bir global kilit olmadan da sistemin tutarlı bir sona ulaşmasını garanti etmektir.
Bu nedenle dağıtık transaction sorunu, tek bir kütüphane ya da tek bir “distributed transaction” framework’ü kurularak çözülmez.
Adımların her birine, o adım başarısız olduğunda ne yapılacağını önceden tanımlamak gerekir. Bu ilkeye compensating transaction diyoruz.
Bu yazıda kullandırım akışını, saldırı yüzeyi yerine hata yüzeyinden başlayıp uçtan uca production tasarımıyla bitecek şekilde adım adım kuracağız:
- Tek transaction hayalinin neden bittiği
- 2PC’nin neden bloke ettiği
- Saga ile adımlara bölme ve telafi tasarımı
- Compensation’ın gerçek (ticari) maliyeti
- Choreography ve orchestration arasındaki seçim
- Sektörden gerçek örnekler
- Ne zaman saga’ya gerek olmadığı
Ana fikir basit:
Dağıtık bir akışta güvenlik, tek bir
ROLLBACKalışkanlığından vazgeçmekle başlar.Önemli not: Saga riski ortadan kaldırmaz, yönetilebilir hale getirir. Hangi adımın telafi edilebileceği ve telafinin bedelini kimin ödeyeceği iş kararıdır, kod kararı değildir.
1. Tek Veritabanında Bu Sorun Yoktu
Monolit bir yapıda kullandırım akışı tek bir işlemdi:
BEGIN;
UPDATE kredi_limit SET bloke = bloke + 50000 WHERE musteri_id = 881;
UPDATE cari_hesap SET bakiye = bakiye + 50000 WHERE musteri_id = 881;
INSERT INTO sigorta_police (...) VALUES (...);
INSERT INTO muhasebe_kaydi (...) VALUES (...);
COMMIT; -- ya hepsi, ya hiçbiri
Bugün limit yönetimi ayrı bir serviste, cari hesap çekirdek bankacılık sisteminde, sigorta üçüncü parti bir sağlayıcıda, muhasebe kendi mutabakat hattında çalışıyor. Aralarında ortak bir transaction sınırı yok ve olmayacak.
Riski yalnızca “hangi adım başarısız olabilir” diye düşünmek eksik kalır. Asıl soru şu:
- Adımlardan biri başarısız olursa önceki adımlar ne olacak?
- Kimse kilit tutarken sistem beklemede mi kalacak?
- Dış sistemler (sigorta sağlayıcısı, MT gibi mesajlaşma ağları) bu koordinasyonu konuşuyor mu?
- Yarım kalan bir kullandırımı kim, nereden görecek?
2. Two-Phase Commit: Kitaptaki Cevap, Sahadaki Sorun
İki fazlı commit (2PC), tam olarak bunu çözmek için tasarlandı. Bir koordinatör var ve iki tur soruyor:
1. FAZ — HAZIRLIK
Koordinatör -> limit-servisi : "hazır mısın?" -> HAZIR (bloke etti)
Koordinatör -> kullandirim-svc : "hazır mısın?" -> HAZIR (bloke etti)
2. FAZ — ONAY
Koordinatör -> herkese : "ONAYLA"
...ya da biri HAYIR dediyse : "İPTAL"
Kağıt üzerinde kusursuz. Peki koordinatör 1. faz ile 2. faz arasında çökerse ne olur?
Bloke Edici Protokol
Limit servisi “hazırım” dedi ve 50.000 TL’yi bloke etti. Şimdi karar bekliyor. Koordinatör yok. Ne yapacak?
- Kendi başına onaylayamaz — belki diğeri hayır demişti.
- Kendi başına iptal edemez — belki diğerleri onayladı.
- Yani bekler. Koordinatör geri gelene kadar.
Bu sırada müşterinin limiti kilitli. Yeni işlem açamıyor, kullandırım tamamlanamıyor, ve müşteri ekrana bakıp bekliyor. Bankacılık akışında bu cümleyi kurabileceğiniz bir senaryo yok.
İkinci ve daha pratik sorun: dış sistemler bu protokolü konuşmuyor. Sigorta sağlayıcısının API’sine “hazır mısın, birazdan onaylayacağım” diyemezsiniz. Onlar tek bir şey biliyor: isteği gönder, sonucu al.
3PC diye bir şey var: araya bir ön-onay fazı koyup koordinatör çökerse katılımcıların zaman aşımıyla ilerlemesini sağlıyor. Bloke etme sorununu azaltıyor ama ağ bölünmesinde hâlâ yanlış karar üretebiliyor ve bir tur daha gecikme ekliyor. Bankacılık tarafında pratikte kullanıldığını görmedim; bilinmesi iyi, kurulması genelde gereksiz.
3. Saga: Adımları Böl, Her Birine Bir Geri Dönüş Yolu Ver
Kullandırım akışını beş bağımsız adıma bölelim. Her adımın ileri hareketi kadar önemli olan, geri dönüş şekli:
| # | Adım | İleri Hareket | Telafi |
|---|---|---|---|
| 1 | Limit blokesi | Kullanılabilir limitten tutarı düş | Bloke edilen tutarı serbest bırak |
| 2 | Kullandırım | Tutarı müşteri hesabına aktar | Ters kayıtla tutarı geri çek |
| 3 | Sigorta poliçesi | Hayat sigortası poliçesi kes, prim tahsil et | Poliçeyi iptal et, primi iade et |
| 4 | Muhasebe kaydı | Genel muhasebeye işle | Geri alınamaz |
| 5 | Müşteri bildirimi | SMS + push gönder | Geri çağrılamaz |
İlk üç adım gerçek anlamda geri alınabilir. Son iki adımda “telafi” diye bir şey yok — sadece işin tamamlandığını farklı bir şekilde ifade edebilirsiniz (düzeltme bildirimi göndermek gibi).
Telafisi olmayan adımı en sona koy. Kod bu ilkeyi kendiliğinden uygulamaz; akışı tasarlarken bilinçli olarak sona bırakmanız gerekir.
4. Compensating Transaction: En Yanlış Anlaşılan Kısım
“Telafi işlemi” kulağa temiz bir ROLLBACK gibi geliyor. Değil. Somut örnek:
14:02:10 Adım 1 tamam: 50.000 TL limit blokelendi
14:02:11 Adım 2 tamam: 50.000 TL müşteri hesabına geçti
14:02:13 Adım 3 BAŞARISIZ: sigorta sağlayıcısı 503 döndürdü (3 deneme sonunda)
14:02:14 TELAFİ: Adım 2 geri alınıyor — ters muhasebe kaydı
14:02:15 TELAFİ: Adım 1 geri alınıyor — limit blokesi kaldırıldı
Sonuç: kullandırım iptal edildi. Ama 5 saniye içinde müşteri hesap
ekstresinde bir giriş bir çıkış gördü. Sigorta sağlayıcısına giden
ilk deneme başarısız olsa da provizyon talebi olarak loglandı —
mutabakat raporunda bu satırın neden orada olduğunu biri
açıklamak zorunda kalacak.
Telafi çalıştı, sistem tutarlı hale geldi — ama iz bıraktı. Bu yüzden telafi kodu yazmadan önce sorulması gereken soru teknik değil:
Bu geri dönüşün müşteri ve mutabakat tarafında görünür bir sonucu olacak mı, ve buna kim karar veriyor?
Üç kural, üretimde ödenen bedelle öğrenildi:
- Geri alınamaz adımı en sona koy. Muhasebe kaydı ve müşteri bildirimi akışın sonunda olmalı.
- Telafi de başarısız olabilir. Sigorta şirketi iptali reddedebilir. Bu noktada otomatik yeniden deneme yerine kaydı manuel müdahale kuyruğuna düşürmek gerekir.
- Telafi idempotent olmalı. Aynı telafi mesajı iki kez işlenirse limit blokesi iki kez serbest bırakılabilir ve müşterinin limiti kullanmadığı halde fazladan açılmış olur.
5. Choreography mi, Orchestration mı?
Choreography: Kimse Merkezi Bilmiyor
Her servis kendi işini yapar, bir olay yayınlar, sıradaki dinler:
limit-servisi : LimitBlokelendi yayınla
kullandirim-svc : LimitBlokelendi dinle -> TutarAktarildi yayınla
sigorta-servisi : TutarAktarildi dinle -> PoliceKesildi yayınla
bildirim-servisi : PoliceKesildi dinle -> (SMS gönder)
Basit, hızlı kurulur, yeni bir dinleyici eklemek ücretsizdir. Ama operasyon ekibi “bu müşterinin kullandırımı ne durumda?” diye sorduğunda, cevap dört ayrı servisin logunda dağılmış durumdadır. Bir adım patladığında telafi zincirini kimin başlatacağı da net değildir.
Orchestration: Akış Tek Bir Yerde, Diskte
Merkezi bir orchestrator adımları sırayla tetikler ve durumu her adımdan önce kalıcı olarak yazar:
public class KullandirimSagaState
{
public Guid SagaId { get; set; }
public string Adim { get; set; } // "LIMIT", "TRANSFER", "SIGORTA", "MUHASEBE", "BILDIRIM"
public string Durum { get; set; } // "DEVAM", "TELAFI", "TAMAMLANDI", "MUDAHALE_GEREKLI"
public string YapilanlarJson { get; set; } // telafi için gereken veri
public DateTime GuncellemeZamani { get; set; }
}
Bu tablo sayesinde “bu kullandırım nerede takıldı?” sorusu tek bir SELECT ile cevaplanır. Orchestrator çökse bile ayağa kalktığında Durum = 'DEVAM' olan kayıtları okuyup kaldığı yerden devam edebilir — yeter ki durum her adımdan önce diske yazılmış olsun.
| Choreography | Orchestration | |
|---|---|---|
| Akış nerede tanımlı | Dağılmış | Tek yerde |
| “Şu an nerede?” sorusu | Birden çok log taraması | Tek sorgu |
| Telafi kararı | Her servis kendi bilir | Merkezden yürütülür |
| Yeni adım eklemek | Kolay | Orchestrator güncellenir |
| Ne zaman tercih edilir | 2-3 adımlık, düşük riskli akış | Çok adımlı, parayla ilgili akış |
6. Sektörden Gerçek Örnekler
Bu pattern kağıt üzerinde kalan bir teori değil; büyük ölçekli sistemlerde somut mühendislik kararlarının arkasında duruyor.
- Uber, sürücü-yolculuk eşleştirme platformunu yeniden mimarlarken çoklu varlık (trip + supply) güncellemelerini tutarlı tutmak için Saga’yı uygulama katmanına taşıdı: koordinatör önce tüm taraflara bir “propose” adımı gönderiyor, hepsi başarılıysa “commit” ediyor, biri başarısız olursa telafi adımlarını tetikliyor.
- Pattern’in akademik kökeni 1987’ye, Hector Garcia-Molina ve Kenneth Salem’in uzun süren transaction’ları küçük, telafi edilebilir adımlara bölme fikrini ortaya attığı makaleye dayanıyor; mikroservis dünyasındaki modern formunu Chris Richardson’ın microservices.io üzerindeki referans yazısı popülerleştirdi.
- Bankacılık, e-ticaret ve seyahat rezervasyonu gibi alanlarda tekrar eden ortak nokta şu: adımlar farklı sistemlere yayılmış ve yarım kalmanın parasal bir karşılığı var — tam da saga’nın çözmeye çalıştığı problem.
Kod tarafında elle state machine yazmak yerine Temporal, Camunda veya AWS Step Functions gibi araçlarla orchestration katmanını hazır altyapıya devretmek de mümkün; MassTransit’in saga state machine’i .NET tarafında bunun daha hafif bir versiyonu.
7. Her Akışa Saga Gerekmez
Saga; kurulumu, telafi kodu ve izlenmesi gereken ek bir karmaşıklık katmanıdır.
Saga’ya gerek yok, eğer:
- Tüm adımlar aynı veritabanına yazıyorsa → tek transaction yeter
- Adımlar birbirinden tamamen bağımsızsa → sıradan bir kuyruk yeter
- Yarım kalması iş açısından sorun değilse → basit retry yeter
Saga gerekiyor, eğer:
- Adımlar farklı servis/sistemlerde çalışıyorsa
- Yarım kalması para veya yasal sonuç doğuruyorsa
- Her adımın tanımlı, uygulanabilir bir telafisi varsa
Sık yapılan hata, tek bir servisin kendi veritabanına yazdığı üç adım için orchestrator kurmaktır. Orada zaten BEGIN...COMMIT var ve sorunsuz çalışıyor.
Sık Yapılan Yanlışlar
❌ “2PC her yerde çalışır, sadece biraz yavaştır.”
✅ Koordinatör çökerse katılımcılar süresiz kilitli kalır; üstelik dış sistemler bu protokolü çoğu zaman konuşmaz.
❌ “Telafi = rollback, aynı şey.”
✅ Telafi geri almaz, tersine çevirir; bunun bazen ticari bir maliyeti olur ve bu maliyeti kim ödeyecek sorusu koda değil sözleşmeye yazılır.
❌ “Saga durumu bellekte tutulabilir, performans için daha iyi.”
✅ Bellekte tutulan durum, ilk restart’ta yarım kalmış işlemleri unutur; durum her adımdan önce diske yazılmalı.
❌ “Choreography her zaman daha esnektir, orchestration’a gerek yok.”
✅ Üç adımdan fazla ve parayla ilgili akışlarda “bu işlem şu an nerede?” sorusuna choreography ile cevap vermek log arkeolojisine dönüşür.
❌ “Telafi mesajı bir kez daha gelirse sorun olmaz, sonuçta zaten telafi.”
✅ Telafi idempotent değilse iki kez işlenen bir telafi, ilk hatadan daha büyük bir tutarsızlık yaratabilir.
❌ “Tek bir servis içindeki üç adım için de saga kuralım, standart olsun.”
✅ Aynı veritabanına yazan adımlar için zaten BEGIN...COMMIT var; saga gereksiz karmaşıklık ekler.
Özet
| Katman / Karar | Çözdüğü Temel Problem |
|---|---|
| 2PC | Tek koordinatörle senkron onay — ama bloke edici |
| Saga (genel) | Kilitsiz, adım adım kesinleşen dağıtık işlem |
| Compensating transaction | Başarısız adımın iş anlamında tersine çevrilmesi |
| Choreography | Basit, az adımlı, düşük riskli akışlar |
| Orchestration | Çok adımlı, kritik, durumu izlenebilir olması gereken akışlar |
| Diske yazılan saga durumu | Orchestrator çökse bile kaldığı yerden devam edebilme |
| İdempotent telafi | Aynı mesajın iki kez işlenmesinin yarattığı ek hasarı önleme |
Production kalitesindeki bir dağıtık akış, hiçbir adımın asla başarısız olmayacağını varsaymaz. Herhangi bir adımın herhangi bir anda başarısız olabileceğini kabul eder ve geri dönüş yolunu koddan önce tasarlar.
Bu konuyu tamamlamak için Event-Driven Architecture ile Ödeme Sistemleri yazısında Saga’nın MassTransit ile production’da nasıl kurulduğunu, Enterprise Core Banking Architecture yazısında ise çekirdek bankacılık sistemlerinin bu tür akışları nasıl barındırdığını inceleyebilirsiniz.
Hap Bilgi
“Dağıtık bir işlemde güvenilir sistem, hiçbir adımın başarısız olmayacağını varsayan sistem değildir; başarısız olan adımın iş anlamında geri döndürülebilmesini önceden tasarlamış sistemdir.”
2PC bunu kilitleyerek çözmeye çalışır. Saga ise kilitlemeden, telafi ederek çözer.
Her zaman olduğu gibi kaynaklara mutlaka göz atılmasını tavsiye ediyorum. ⬇️
- Uber Engineering — Fulfillment Platform Re-architecture (Saga ile çoklu-varlık transaction yönetimi)
- Chris Richardson — microservices.io, “Pattern: Saga”
- Garcia-Molina & Salem (1987) — Saga kavramının ortaya çıktığı akademik makale
- MassTransit Documentation — Saga State Machine
