idempotent api design, yazılım dünyasının “müşteri butona iki kere bastı diye iki kere para çekmeyelim kardeşim” diye ortaya çıkmış akıl sağlığı mekanizmasıdır.

ismi biraz akademik durur. ilk duyunca sanki distributed systems profesörü kahvesini yudumlarken söylemiş gibi gelir. ama gerçek hayattaki karşılığı gayet basittir:

aynı işlem birden fazla kez gelirse sistem sapıtmayacak.

bu kadar.

mesela kullanıcı ödeme yapıyor. internet bir gidip geliyor. mobil uygulama “acaba istek gitti mi?” diye tekrar gönderiyor. kullanıcı da panikle butona bir daha basıyor. backend tarafında biri bu ihtimali düşünmediyse aynı sipariş için iki ödeme, iki fatura, iki bildirim, üç tane müşteri destek kaydı ve bir tane sinir krizi doğar.

işte idempotent api design burada kapıdan girer.

der ki:

“aynı işlem mi geldi? tamam, ben bunu daha önce yapmış mıyım bakayım. yaptıysam tekrar yapmayayım, önceki sonucu döneyim.”

fintech’te, ödeme sistemlerinde, cüzdan uygulamalarında, sipariş sistemlerinde, stok yönetiminde, kargo entegrasyonlarında, fatura kesme süreçlerinde bu konu lüks değil, temel ihtiyaçtır.

çünkü network güvenilmezdir.
client güvenilmezdir.
kullanıcı hiç güvenilmezdir.
mobil internet zaten başlı başına karakter testidir.

sen backend’i “istek bir kere gelir, tertemiz işlenir, herkes medeni davranır” diye tasarlarsan production seni yakasından tutup gerçek hayata çıkarır.

idempotent tasarımın en bilinen yolu idempotency key kullanmaktır.

client her kritik işlem için benzersiz bir anahtar üretir. örnek:

```text
ıdempotency-key: payment_8f4f2d9a-7b12-4c2a-9f02
```

sonra backend bu key’i görür.

ilk geldiyse işlemi yapar, sonucu kaydeder.
aynı key tekrar geldiyse işlemi yeniden yapmaz, eski sonucu döner.

yani kullanıcı butona tekrar basmış, mobil uygulama retry atmış, proxy isteği yeniden göndermiş, client timeout almış; sistem bunlara trip atmaz. “ben bu işi zaten yaptım” der.

burada kritik nokta şu: idempotency key süs değildir.

sadece header’a key koymakla idempotent olunmaz. backend tarafında o key’in atomik şekilde kontrol edilmesi, işlem sonucu ile beraber saklanması, aynı key ile farklı payload gelirse kavga çıkarılması, ttl veya saklama süresinin doğru belirlenmesi gerekir.

yoksa sisteme `ıdempotency-key` eklemiş olursun ama hâlâ iki kere para çekersin. bu da teknik olarak güvenlik kemeri takıp arabayı duvara sürmeye benzer.

idempotent api tasarımında en sık yapılan hatalardan biri şudur:

“biz transaction kullanıyoruz, sorun olmaz.”

olur.

transaction veritabanı içinde tutarlılık sağlar. ama aynı request’in iki kere gelip iki ayrı transaction açmasını tek başına engellemez. sen aynı ödeme isteğini iki kere kabul ediyorsan, transaction ikisini de gayet namuslu şekilde işler. veritabanı da “ben verilen görevi yaptım” der, kenara çekilir.

diğer hata:

“frontend butonu disable ediyoruz.”

çok güzel. peki mobil uygulama crash olursa? ağ kesilirse? client timeout alırsa? kullanıcı eski request’i tekrar gönderirse? reverse proxy retry yaparsa? webhook karşı tarafça tekrar denenirse?

frontend önlemi iyidir ama backend güvencesi değildir.

bir ödeme sistemi tasarlıyorsan şunu kafaya yazacaksın:

client tarafı nezaket kuralıdır.
backend tarafı hukuk kuralıdır.

idempotency key özellikle post isteklerinde hayat kurtarır. çünkü get, put, delete gibi metodların http semantiğinde daha idempotent düşünülmesi beklenir. ama post genelde “yeni bir şey oluşturuyorum” kafasında olduğu için tekrarlandığında yeni kayıt, yeni ödeme, yeni sipariş, yeni mail, yeni felaket doğurabilir.

mesela:

```text
post /payments
post /orders
post /wallet/withdraw
post /invoices
post /shipments
post /email/send
```

bunlar yanlış tasarlanırsa tekrar tekrar çalışır.

kullanıcı bir kere para gönderir, sistem iki kere işler.
bir kere fatura kesilir, iki fatura oluşur.
bir kere kargo talebi açılır, üç gönderi oluşur.
bir kere sms gitmesi gerekir, kullanıcıya beş sms gider.

sonra herkes “nasıl oldu?” diye loglara bakar.

nasıl olduğu belli kardeşim. idempotency yok.

iyi tasarım şöyle olur:

```text
post /payments
ıdempotency-key: abc-123

request body:
{
"order_id": "ord-1001",
"amount": 5000,
"currency": "try"
}
```

backend der ki:

bu key daha önce geldi mi?
geldiyse payload aynı mı?
işlem tamamlandı mı?
tamamlandıysa önceki response’u dön.
devam ediyorsa 409 veya uygun bir durum dön.
başarısız olduysa retry politikası ne?
aynı key ile farklı tutar geldiyse bunu reddet.

işte bu apı tasarımıdır.

yoksa sadece endpoint açmak değildir.

idempotent tasarımda response da önemlidir. aynı istek tekrar geldiğinde illa aynı status code dönmek zorunda olmayabilir ama client açısından sonuç anlaşılır olmalıdır. mesela ilk delete isteği 200 döner, sonraki 404 dönebilir; ama kaynak zaten silinmiş olduğu için server state açısından işlem tekrarlandığında yeni bir yıkım oluşmaz. mesele “cevap birebir aynı mı?” değil, “sisteme etkisi aynı mı?” meselesidir.

ödeme sistemlerinde ise genelde önceki response’u saklamak daha sağlıklıdır. çünkü client “ödeme başarılı mı, başarısız mı, pending mi?” sorusuna net cevap ister.

idempotency key saklama tablosu basitçe şöyle olabilir:

```text
idempotency_key
request_hash
status
response_body
resource_id
created_at
expires_at
```

aynı key geldiğinde hash karşılaştırılır.

aynı key + aynı body ise eski sonuç döner.
aynı key + farklı body ise “hayırdır kardeşim?” denir.

çünkü aynı idempotency key ile 100 tl ödeme isteyip sonra 500 tl göndermek normal değildir. bu ya client bug’ıdır ya da sistemin kaderiyle oynayan biridir.

bir de concurrency meselesi var.

iki aynı istek aynı anda gelirse ne olacak?

işte burada iş ciddileşir.

önce idempotency kaydı oluşturayım, sonra ödeme yapayım dersen yarış durumu doğabilir. iki process aynı anda “key yok” görüp ikisi de işlem yapabilir. bu yüzden unique constraint, row lock, transaction, atomic insert gibi mekanizmalar gerekir.

örnek:

```text
idempotency_key alanı unique olacak.
ilk request insert edecek.
ikinci request unique constraint’e çarpacak.
sonra gidip mevcut kaydın sonucunu bekleyecek veya dönecek.
```

bu yoksa idempotency değil, iyi niyet protokolü kurmuş olursun.

webhook tarafında idempotency daha da önemlidir.

ödeme sağlayıcıları, kargo firmaları, fatura sistemleri, banka entegrasyonları çoğu zaman webhook’u tekrar gönderir. çünkü karşı taraf senin 200 ok dönüp dönmediğine, timeout alıp almadığına, kendi retry politikasına göre tekrar dener.

sen webhook’u her geldiğinde “yeni olay” sanırsan sistem kendini çoğaltmaya başlar.

mesela:

```text
payment.succeeded
payment.succeeded
payment.succeeded
```

aynı event üç kere geldi diye müşterinin bakiyesine üç kere para yazılmaz. event id tutulur, işlenmiş mi bakılır, işlendiyse geçilir.

yani webhook consumer yazarken ilk kural:

“bu event tekrar gelebilir.”

bunu kabul etmeyen sistem, dağıtık sistemler dünyasında fazla masumdur.

idempotent api design biraz da olgunluk göstergesidir.

junior sistem şöyle düşünür:

“butona basınca endpoint çalışsın.”

senior sistem şöyle düşünür:

“butona iki kere basılırsa ne olur? request timeout olursa ne olur? retry gelirse ne olur? aynı anda iki istek gelirse ne olur? response kaybolursa ne olur? webhook tekrar gelirse ne olur? işlem yarıda kalırsa ne olur?”

işte aradaki fark budur.

bir de retry meselesi var.

modern sistemlerde retry kaçınılmazdır. queue retry yapar, client retry yapar, load balancer bazen tekrar dener, job worker hata alınca yeniden çalışır. retry kötü değildir. kötü olan, retry’a dayanıklı olmayan iş mantığıdır.

idempotent olmayan sistemde retry, hata düzeltme mekanizması değil, felaket çoğaltma mekanizmasıdır.

mesela mail gönderme işlemi.

kullanıcıya şifre sıfırlama maili gidecek. request iki kere geldi. iki mail çok büyük felaket olmayabilir. ama para transferinde aynı rahatlık yok. cüzdan bakiyesinde, hisse alım emrinde, fatura kesmede, stok düşmede, kargo oluşturmada ikinci işlem gerçek zarardır.

bu yüzden her endpoint aynı ciddiyette ele alınmaz.

idempotency özellikle şuralarda şarttır:

ödeme alma
para transferi
cüzdan yükleme
cüzdan çekim
fatura oluşturma
sipariş oluşturma
stok düşme
abonelik başlatma
kargo oluşturma
webhook işleme
background job çalıştırma
event consumer işlemleri

“okuma” endpointlerinde zaten mesele daha basittir. `get /orders/123` aynı state’i değiştirmediği sürece idempotent sayılır. ama state değiştiren yerde işin rengi değişir.

idempotent api design, sadece backend konusu da değildir. ürün tasarımı, mobil uygulama, entegrasyon dokümanı, müşteri destek süreci bile bundan etkilenir.

api dokümanında şunlar yazmalı:

idempotency key hangi endpointlerde zorunlu?
key ne kadar süre saklanır?
aynı key ile farklı body gelirse ne olur?
ilk işlem pending ise tekrar isteğe ne döner?
timeout alan client ne yapmalı?
webhook event id nasıl kullanılmalı?
response tekrar döner mi?
hangi hata kodları retry edilebilir?

bunlar yazmıyorsa entegratör kafasına göre davranır. sonra sen “müşteri yanlış kullanmış” dersin. müşteri de “dokümanda yazmıyordu” der. ikiniz de haklı gibi görünürsünüz ama sistem yine patlamıştır.

idempotency key’in ömrü de önemlidir.

sonsuz saklarsan tablo çöplüğe döner.
çok kısa saklarsan retry geldiğinde eski işlemi unutursun.
ödeme gibi kritik işlerde saklama süresi işin doğasına göre belirlenmelidir.

mesela 24 saat, 48 saat, 7 gün; bu tamamen sistemin retry ve mutabakat mantığına göre değişir. ama “hiç düşünmedik” kabul edilebilir cevap değildir.

bir de kvkk ve güvenlik tarafı var.

idempotency tablosuna request body’yi komple basmak pratik görünebilir ama içinde kişisel veri, kart dışı hassas bilgi, telefon, adres, tckn, iban gibi alanlar varsa yeni bir veri çöplüğü oluşturmuş olursun. daha doğru yaklaşım çoğu zaman request hash tutmak, gerekli minimum metadata ile response referansı saklamaktır.

yani idempotent olayım derken kişisel veriyi gereksiz yere çoğaltırsan, teknik borcu hukuki borca çevirmiş olursun.

güzel tasarımda şunlar olur:

unique idempotency key
request hash kontrolü
minimum veri saklama
ttl/retention politikası
atomic insert
transaction sınırı
eski response’u dönebilme
pending durumunu yönetme
aynı key farklı payload kontrolü
event id bazlı webhook deduplication
loglarda key maskeleme veya dikkatli kullanım

kötü tasarımda ise şunlar olur:

frontend butonu disable ettik, yeter.
client aynı isteği atmaz herhalde.
webhook zaten bir kere gelir.
transaction var, sorun olmaz.
timeout olursa kullanıcı tekrar dener.
loglara komple body basalım.
idempotency key var ama unique constraint yok.
aynı key farklı payload ile gelirse de işlemeye devam.

bu listeyi görünce production incident kokusu geliyor.

idempotent api design aslında sistemin şu cümleyi diyebilmesidir:

“panik yapma, bunu daha önce gördüm.”

dağıtık sistemlerde bu cümle çok kıymetlidir.

çünkü her şey olabilir.

internet gider.
client timeout alır.
worker ölür.
message queue tekrar yollar.
webhook yine gelir.
kullanıcı iki kere basar.
mobil uygulama offline’dan dönerken eski isteği yollar.
load balancer naz yapar.
database geç cevap verir.

sen bunların hepsine “ama normalde böyle olmaması lazım” diye yaklaşırsan, sistem mimarisi değil dilekçe yazmış olursun.

özetle idempotent api design şudur:

aynı istek tekrar geldiğinde sistemi bozmamak.
kritik işlemleri tekrar çalıştırmamak.
önceki sonucu doğru şekilde dönebilmek.
retry dünyasında güvenle yaşayabilmek.
ödeme, cüzdan, sipariş, webhook gibi yerlerde ikinci felaketi engellemek.

iyi api tasarımı sadece doğru cevabı ilk seferde vermek değildir.

aynı soru yanlışlıkla beş kere sorulduğunda da aynı olgunlukla cevap verebilmektir.

idempotency bunu sağlar.

ve özellikle para işlerinde şunu öğretir:

kullanıcı butona iki kere basabilir.
client isteği tekrar gönderebilir.
webhook tekrar gelebilir.
ama sistem ikinci kez para çekemez.

çekiyorsa o artık api değil, kumar makinesidir.
1 1
ayı kullanıcısının profil fotografı