Oyun Kitabı
API (Application Programming Interface) İsteğinin Ötesinde Idempotency (Tekrarlanabilir Güvenli İşlem) (API İsteginin Otesinde İdempotency)
Idempotency tek bir header değildir. API anahtarından adım işaretçisine kadar beş farklı katmanda ayrı ayrı kurulması gereken bir savunma yığınıdır.
Dağıtık Ödeme Motoru (Distributed Payment Engine)
Bolum 5 / 22
Capture ile complete arasındaki boşluğu kapatan dağıtık ödeme mimarisi serisi.
Idempotency bir header değil, bir yığındır
Çoğu ekip idempotency'i “API isteğine bir Idempotency-Key header'ı eklemek” olarak öğrenir ve orada bırakır. Ödeme sistemlerinde bu, buzdağının sadece görünen ucudur. Aynı işlem, sisteminizden en az beş farklı noktadan “tekrar” geçebilir: istemcinin retry'ı, PSP'nin webhook tekrarı, mesaj kuyruğunun at-least-once teslimatı, worker'ın job'ı yeniden alması, saga adımının yeniden çalışması.
Client retry → API idempotency key
PSP retry → Gateway event id
Broker retry → Inbox kaydı
Worker retry → Job uniqueness
Saga retry → Step marker
Bu bölüm, idempotency'i tek bir noktada değil, bu beş katmanın her birinde ayrı ayrı nasıl kuracağınızı anlatıyor.
Kavramlar ilk geçtiği yerde
📦 API Idempotency Key
İstemcinin gönderdiği, aynı isteğin tekrar gönderilmesi halinde aynı sonucu garanti eden benzersiz anahtar.
📦 Gateway Event ID
PSP'nin her webhook veya bildirime verdiği benzersiz kimlik; aynı olayın birden fazla teslimatını ayırt etmeye yarar.
📦 Inbox
Gelen bir olayın işlenmeden önce kaydedildiği, tekrarları filtreleyen dayanıklı bir tablo.
📦 Job Uniqueness
Bir arka plan işinin (job) aynı iş anahtarıyla ikinci kez kuyruğa girmesini önleyen kısıt.
📦 Step Marker
Bir saga adımının tamamlandığını kalıcı olarak işaretleyen, yeniden çalışmayı önleyen kayıt.
Bu beş kavram birbirinin yerine geçmez. API idempotency key, istemci ile sizin aranızdaki tekrarı önler; ama PSP'nin webhook'unu, kuyruğunuzun teslimatını veya worker'ınızın job'ını hiç etkilemez.
Katman 1: API isteği
Müşteri “Öde” butonuna iki kez basarsa (ağ gecikmesi, çift tıklama), istemci aynı Idempotency-Key ile isteği gönderir. Sunucu bu anahtarı görmüşse, işlemi tekrar çalıştırmaz; ilk çalıştırmanın sonucunu döner. Bu katman, tamamen sizin API'niz ile istemci arasındaki bir sözleşmedir.
POST /payments Idempotency-Key: abc123
→ ilk çağrı: işlem çalışır, sonuç kaydedilir
→ aynı key ile ikinci çağrı: kayıtlı sonuç döner, işlem tekrar çalışmaz
Katman 2: PSP'den gelen olay
PSP, aynı olayı (örneğin “capture başarılı”) ağ sorunları veya kendi retry politikası nedeniyle birden fazla kez gönderebilir. Bu olayların her birinde PSP'nin verdiği benzersiz bir event id vardır. Bu id'yi görmüşseniz, olayı tekrar işlememelisiniz — ama işlemediğinizi PSP'ye de açıkça bildirmelisiniz (ACK).
Webhook #1: event_id=evt_001, type=payment.captured
Webhook #2: event_id=evt_001, type=payment.captured (tekrar teslim)
→ aynı event_id görülmüşse, işlem atlanır, 200 OK döner
Katman 3: Mesaj kuyruğu / Inbox
Event id kontrolü, olayı doğrudan işleyen kod içinde yapılırsa, aynı olayın iki farklı worker tarafından yarışarak işlenmesi (race condition) riskini almış olursunuz. Bu yüzden inbox deseni kullanılır: olay önce benzersiz bir kısıtla (unique constraint) inbox tablosuna yazılır; bu yazma başarısızsa (zaten var), olay zaten görülmüştür ve işlem güvenle atlanır.
INSERT INTO inbox (event_id, ...) VALUES ('evt_001', ...)
→ başarılı: ilk kez görülüyor, işlem kuyruğuna eklenir
→ unique constraint hatası: zaten görülmüş, sessizce atlanır
Katman 4: Job'ın kendisi
Inbox'tan sonra iş, bir arka plan job'ına devredilir. Bu job da kendi başına birden fazla kez kuyruğa girebilir (örneğin bir retry mekanizması veya bir yeniden deploy sırasında). Job kuyruğu, aynı iş anahtarına (örneğin payment_id + step_name) sahip ikinci bir job'ı reddetmelidir.
Job key: payment_id=pay_42, step=finalize_stock
→ aynı key ile ikinci job denemesi: kuyruk seviyesinde reddedilir
Katman 5: Saga adımının kendisi
Job çalışırken bile, adımın kendisi idempotent olmalıdır — çünkü job'ın kendisi de bir crash sonrası yeniden çalışabilir. Bu son katman, üçüncü bölümde tanıttığımız adım işaretçileridir: her adım, kendi tamamlandığını kalıcı olarak işaretler, ve adım tekrar çalıştırılırsa bu işareti kontrol ederek gerçek işi (stok düşme, finans kaydı açma) tekrar yapmaz.
Step marker: finalize_stock=DONE (payment_id=pay_42)
→ adım tekrar çağrılırsa, marker kontrol edilir, iş tekrar yapılmaz
Neden bu beş katman ayrı ayrı gereklidir
Her katman, farklı bir “kim tekrar gönderiyor” sorusuna cevap verir: istemci mi, PSP mi, kuyruk mu, worker mı, yoksa job'ın kendisi mi. Sadece API katmanında idempotency kurup diğer dördünü atlarsanız, sisteminiz “API'de tek, ama arka planda üç kez” çalışan bir finalization saga'sına sahip olabilir — ve bu hatayı yalnızca üretimde, genellikle bir müşteri şikayetiyle fark edersiniz.
Bu bölümde en çok karışan eşleştirmeler
❌ Idempotency-Key header'ı eklemek, idempotency problemini çözer
✓ Bu header sadece istemci-API katmanındaki tekrarı çözer
❌ PSP event id kontrolü tek başına yeterlidir
✓ Event id kontrolü, race condition'a karşı bir inbox/unique constraint ile desteklenmelidir
❌ Job kuyruğu at-least-once teslimat yaparsa, iş otomatik olarak idempotent olur
✓ At-least-once teslimat + idempotent olmayan iş = güvenli değil; iş kendi başına idempotent olmalıdır
❌ Bir saga adımı başarıyla tamamlandıysa tekrar çağrılması zararsızdır
✓ Adım işaretçisi yoksa tekrar çağrı, yan etkiyi (stok düşme, ödeme) ikinci kez tetikler
Idempotency yığınınızı denetleme listesi
- API'nizde idempotency key var mı? Varsa, sonucu ne kadar süre saklıyorsunuz?
- PSP webhook'larınızda event id kontrolü yapıyor musunuz, yoksa her webhook'u doğrudan mı işliyorsunuz?
- Inbox tablonuzda event id için unique constraint var mı, yoksa kontrolü uygulama kodunda mı yapıyorsunuz (race condition riski)?
- Job kuyruğunuz aynı iş anahtarıyla ikinci bir job'ı reddediyor mu?
- Her saga adımı, kendi tamamlandığını kontrol eden bir marker'a mı bakıyor, yoksa her çalıştığında işi baştan mı yapıyor?
Bu beş katmandan ikisi eksikse, sisteminiz muhtemelen “nadiren ama tekrarlanan” çift işlem hatalarına açıktır.
Bu bölümden aklında kalması gerekenler
- Idempotency tek bir header veya tek bir kontrol değildir; en az beş bağımsız katmanda ayrı ayrı kurulması gereken bir yığındır.
- Her katman, farklı bir tekrar kaynağına (istemci, PSP, kuyruk, worker, saga adımı) cevap verir; biri diğerini kapsamaz.
- Inbox deseni, event id kontrolünü race condition'a karşı güvenli hale getiren yapısal bir çözümdür, sadece bir “if” kontrolü değildir.
- At-least-once teslimat modelinde idempotent olmayan bir iş adımı, er ya da geç iki kez çalışır.
Idempotency'i bir header'a indirgemek, beş katlı bir binaya tek bir kapı koyup diğer dört katın penceresini açık bırakmaktır.
SSS
Sık sorulan sorular
API Idempotency Key nedir?
İstemcinin gönderdiği, aynı isteğin tekrar gönderilmesi halinde aynı sonucu garanti eden benzersiz anahtar.
Gateway Event ID nedir?
PSP'nin her webhook veya bildirime verdiği benzersiz kimlik; aynı olayın birden fazla teslimatını ayırt etmeye yarar.
"Idempotency-Key header'ı eklemek, idempotency problemini çözer" doğru mu?
Bu header sadece istemci-API katmanındaki tekrarı çözer
Bu bölüm neyi sabitler?
Bu bölüm, idempotency'i tek bir noktada değil, bu beş katmanın her birinde ayrı ayrı nasıl kuracağınızı anlatıyor. Idempotency tek bir header veya tek bir kontrol değildir; en az beş bağımsız katmanda ayrı ayrı kurulması gereken bir yığındır. Çoğu ekip idempotency'i “API isteğine bir `Idempotency-Key` header'ı eklemek” olarak öğrenir ve orada bırakır. Ödeme sistemlerinde bu, buzdağının sadece görünen ucudur. Aynı işlem, sisteminizden en az beş farklı noktadan “tekrar” geçebilir: istemcinin retry'ı, PSP'nin webhook tekrarı, mesaj kuyruğunun at-least-once teslimatı, worker'ın job'ı yeniden alması, saga adımının yeniden çalışması.
Ogrenilen Muhendislik Prensipleri
- Idempotency, tek bir header değil; API, gateway, inbox, job ve saga adımı katmanlarında ayrı ayrı kurulması gereken bir yığındır.
- Her katman farklı bir tekrar kaynağına cevap verir; birini kurup diğerlerini atlamak sistemi yarı korumalı bırakır.
- At-least-once teslimat modelinde idempotent olmayan bir adım, er ya da geç mutlaka iki kez çalışır.
Okumaya devam et
Okumaya devam et
Seride sonraki yazi
Ödeme Sistemlerinde Webhook Güvenilirliği
Webhook'lar tekrarlanır, kaybolur, sırasız ve gecikmeli gelir. İmzayı doğrulayın, hızlı ACK verin, ağır işi asla senkron çalıştırmayın.
Seride sonraki yazi
Değişmez Ödeme Snapshot Tasarımı: Sepeti Donduran Karar
Ödeme başlarken sepeti canlı okumak, tutarı ve para birimini kararsız bırakır. Intent anında donan bir snapshot olmadan finalization güvenilir çalışmaz.
Ayni seriden
Ödeme Sistemlerinde Outbox/Inbox Pattern
Veritabanına yazmak ile event yayınlamak aynı transaction'da değilse, biri kaybolur ya da tekrarlanır. Outbox yayınlar, inbox tüketicide dedup eder.