Şubat başında bir cumartesi öğlen, İzmir'de aniden bastıran fırtına yüzünden kurye bulunamayan birkaç yüz sipariş üst üste iptal edildi ve müşteri destek panosunda daha önce hiç görmediğim bir kategori patladı: "İptal ettim ama param iade edilmedi, üstelik yemek kapıma geldi." Tek cümlede üç ayrı sistemin çelişkisi: sipariş servisi iptali kabul etmiş, ödeme servisi iadeyi yapamamış, restoran entegrasyonu iptal mesajını hiç almamış. O gün 214 siparişte bu üçlünün en az ikisi birbiriyle çelişiyordu. Monolit günlerinde bu iş tek bir veritabanı transaction'ıydı: ROLLBACK yazarsınız, biter. Mikroservis dünyasında ise bir iptal, dağıtık bir dramadır — ve bizim sahnemizde o gün suflör yoktu.
İptal Dediğin Tek Bir İş Değil, Beş İşin Pazarlığı
Bir yemek siparişinin iptali bizde beş servisi ilgilendiriyor: sipariş durumunu güncelleyen çekirdek servis, ödemeyi iade eden ödeme servisi, restorana "hazırlamayı durdur" diyen entegrasyon servisi, kurye atamasını çözen lojistik servisi ve kuponu/puanı iade eden kampanya servisi. Bu beşlinin ortak transaction'ı yok ve olamaz; ödeme sağlayıcısı harici bir kurum, restoran entegrasyonu üçüncü parti bir POS sistemi. İki fazlı commit gibi dağıtık kilit çözümleri bu heterojenlikte hayal. Geriye tek gerçekçi desen kalıyor: saga — işi yerel adımlara böl, her adımın telafisini (compensation) tanımla, bir adım kalıcı olarak başarısız olursa öncekileri telafi işlemleriyle geri sar.
İtiraf: bizim sistemde saga "vardı". Yani birileri yıllar önce Kafka üzerinden koreografi kurmuştu — sipariş servisi OrderCancelled yayınlar, dinleyen herkes kendi işini yapar. Kağıtta zarif, pratikte sahipsiz. O cumartesi olan şuydu: ödeme sağlayıcısının iade API'si fırtına trafiğiyle alakasız bir sebepten yüzde 40 hata dönmeye başladı. Ödeme servisi iade olayını işleyemedi ve mesajları üç denemeden sonra DLQ'ya attı. Ama zincirin geri kalanı bundan habersizdi: sipariş çoktan "iptal edildi" görünüyordu, restoran entegrasyonu ise ayrı bir hata yüzünden (POS sağlayıcısında timeout) mesajı hiç işleyememişti. Koreografide herkes kendi dansını biliyordu; dansın bütününü izleyen kimse yoktu. "Bu iptal şu anda hangi durumda?" sorusunun cevabı sistemde hiçbir yerde yazmıyordu — beş servisin loglarının kesişiminde, insan zekasıyla türetilmesi gerekiyordu.
Koreografiden Orkestrasyona: Sahneye Bir Şef Koymak
İlk tartışmada güçlü bir karşı görüş vardı: "Koreografi gevşek bağlıdır, orkestratör tek hata noktasıdır." Teoride katılıyorum; pratikte, telafi gerektiren ve insanlara para iadesi gibi geri dönüşü olmayan yan etkiler içeren akışlarda görünürlük her şeyden önce gelir. İptal akışını açık bir orkestratöre taşıdık: sipariş servisi içinde yaşayan, her iptali bir durum makinesi olarak PostgreSQL'de saklayan bir saga yöneticisi. Hazır bir saga framework'ü değerlendirdik, sonra vazgeçtik; ihtiyacımız olan şey 600 satırlık, sıkı test edilmiş, adım-durum-telafi üçlüsünü yöneten bir çekirdekti ve onu ekip iki haftada yazdı. Her iptal kaydı artık şu alanları taşıyor: adım listesi, her adımın durumu (bekliyor, tamam, başarısız, telafi edildi), deneme sayıları, son hata ve bir sonraki uyanma zamanı.
Asıl zor iş orkestratör değil, telafilerin kendisiydi. Telafi işlemi yazarken öğrendiğimiz acı gerçek: her telafinin de bir başarısızlık senaryosu var. İade çağrısı başarısız olursa ne olur? Tekrar denersiniz. Peki iade çağrısı timeout ile öldü ama sağlayıcı tarafında aslında gerçekleştiyse? Tekrar denediğinizde müşteriye çift iade yaparsınız — bunu ilk hafta 12 siparişte bizzat başardık. Çözüm yine idempotency: her iade isteği, sipariş kimliğinden türetilen bir idempotency anahtarı taşır ve sağlayıcının bu anahtarı desteklemediği tek entegrasyonda, iade öncesi "bu anahtarla iade var mı?" sorgusu atılır. Restoran tarafında ise telafi bambaşka bir hayvan: mutfak pideyi fırına verdiyse hiçbir API çağrısı onu geri çıkaramaz. Orada telafi teknik değil ticari: iptal, hazırlığın hangi aşamasında geldiyse ona göre restorana otomatik tazminat tahakkuku yapılır. Saga tasarımı bize şunu öğretti: telafi her zaman "geri al" değildir; bazen "zararı adil paylaştır"dır.
Takılı Sagalar ve Zaman Aşımının Politikası
Orkestrasyona geçince sorunlar bitmedi, görünür oldu — ki amaç da buydu. Yeni Grafana panelimizde "aktif saga yaş dağılımı" grafiği var ve ilk haftalarda kuyruğun ucunda hep birkaç düzine "takılı" saga birikiyordu: ödeme sağlayıcısı yanıt vermiyor, POS entegrasyonu bakımda, kurye ataması yarış durumunda. Her takılı saga için üç politika tanımladık. Kısa vadede exponential backoff ile otomatik yeniden deneme — 30 saniyeden başlayıp 15 dakikaya uzanan aralıklarla, en fazla 8 deneme. Orta vadede eskalasyon: 30 dakikayı aşan sagalar operasyon ekibinin iş kuyruğuna düşer ve panelde turuncuya döner. Uzun vadede ise en kritik karar: hangi adım başarısızsa müşteri ne görür? Kuralımız net: müşteriye dönük sonuç (iptal onayı ve iade taahhüdü) saga'nın ilk adımında kesinleşir; iç dünyanın pazarlıkları müşteriye asla yansımaz. İade taahhüt edilir; sağlayıcı üç saat sonra da dönse, taahhüt saati üzerinden takip edilir.
Rakamlar dönüşümü anlatıyor: geçişten önceki ay, iptal kaynaklı tutarsızlık nedeniyle destek ekibinin elle düzelttiği vaka sayısı 340'tı; geçişten sonraki ikinci ayda 9. Ortalama iade tamamlanma süresi 4,2 saatten 11 dakikaya indi; p99 hâlâ 3 saat civarı ama artık o kuyrukta kimin neden beklediğini isim isim biliyoruz. Ve belki en değerlisi: fırtına gibi bir dış şok geldiğinde artık "kaç sipariş askıda?" sorusunun cevabı bir SQL sorgusu, altı kişilik bir log kazısı değil.
Test tarafında da bir alışkanlık değişti. Saga'ların birim testi kolay, asıl mesele kaos senaryoları: her adımın her aşamasında süreci öldürüp yeniden başlatan bir test koşucusu yazdık — adım öncesi, adım sırası, adım sonrası, telafi sırası. İptal akışının 5 adımı çarpı 4 kesinti noktası, 20 senaryo; her biri CI'da her gece koşuyor ve durum makinesinin hiçbir kombinasyonda "tanımsız" duruma düşmediğini doğruluyor. Bu koşucu ilk ay iki gerçek bug yakaladı; ikisi de "telafi sırasında ölürsek" dalındaydı — yani tam olarak, üretimde en nadir ama en pahalı anda ortaya çıkacak türden.
Dramanın Ahlakı
Saga desenini konferans sunumlarından öğrenirseniz zarif bir diyagram görürsünüz: ileri oklar, geri oklar, simetrik telafiler. Üretimde öğrenirseniz şunu görürsünüz: telafiler asimetriktir, dış dünya idempotent değildir, fırın geri çevrilemez ve en önemli tasarım kararı teknik değil, kimin hangi belirsizliğe katlanacağının kararıdır. Biz belirsizliği müşteriden alıp kendi operasyon kuyruğumuza taşıdık; bu bir algoritma değil, bir taahhüt. Dağıtık transaction derdini yaşıyorsanız — özellikle "iptal/iade akışımız ara sıra tuhaflaşıyor" cümlesi tanıdıksa — buradan ulaşın; durum makinesi şemamızı ve telafi politika tablomuzu paylaşmaktan memnuniyet duyarım.
O cumartesi kapısına hem iade hem yemek giden müşterilerden birine destek ekibi durumu açıklamış. Müşterinin cevabı ekipte hâlâ anlatılır: "Sisteminiz bozulunca cömertleşiyorsa, bence haftada bir bozulsun." Saga'yı düzgün kurduk; cömertliği artık fırtınalara değil, kampanya bütçesine bağladık.