REST Tasarımı ve Idempotency
30 saniyede özet
Bir isteğin cevabı gelmezse iki ihtimal var: istek hiç ulaşmadı ya da iş yapıldı ama cevap yolda kayboldu. İstemci bunları ayıramaz. Tekrar denenecek her istek, iki kez gelse de işi bir kez yapmalı.
Bir arkadaşına mesaj attın ve cevap gelmedi. Mesaj ulaşmadı mı, yoksa cevabı mı kayboldu? Ağ üzerinden yapılan her çağrı da böyle üç şekilde biter: başarı, açık hata ve bilinmeyen.
-
Bayt: Müşteri bir kez sipariş verdiğine yemin ediyor, ama sistemde üç sipariş var!
-
Sen: Belki üç kez tıkladı?
-
Bayt: Tek tıkladı. Uygulama cevap alamayınca kendi kendine iki kez daha denedi.
-
Bayt: Peki sunucu ilk isteği almış mıydı, almamış mıydı?
Adım adım oku
- İstemci siparişi gönderir; sunucu işi yapar ve siparişi kaydeder.
- Cevap dönüş yolunda kaybolur; istemci yalnızca zaman aşımı görür.
- İstemci körü körüne tekrar denerse sunucu ikinci bir sipariş oluşturur.
- Aynı Idempotency-Key ile tekrar gelirse sunucu anahtarı tanır, işi yapmaz ve sakladığı cevabı döner.
İki farklı kayıp, istemci için aynı görünür
İstek kayboldu: istemci ──✕ sunucu → hiçbir şey olmadıCevap kayboldu: istemci ────────→ sunucu ✕─── → İŞ YAPILDIİstemci her iki durumda da timeout alır. Aradaki farkı göremez. Tekrar denerse:
- İstek kaybolmuşsa → doğru davranış, iş bir kez yapılır
- Cevap kaybolmuşsa → iş ikinci kez yapılır
Kafam karıştı, daha basit anlat
Cevap gelmediğinde iki ihtimal var: mektup hiç ulaşmadı ya da ulaştı ama cevap yolda kayboldu. Senin tarafından ikisi aynı görünür.
Kendin gör
Idempotency — ağ güvenilmezken tekrar denemek
Tohum 1POST /ordersDenemeler
henüz gönderilmedi
Sunucudaki kayıtlar
— kayıt yok —
Şu an ne oldu?
1. deneme gönderiliyor
İstemci isteği ilk kez gönderiyor.
Aklında kalsın: Retry mekanizması kaçınılmazdır — timeout, yeniden başlatma, yük dengeleyici hepsi tekrar üretir. Soru retry yapılıp yapılmayacağı değil, tekrarın güvenli olup olmadığıdır.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
Varsayılan: anahtarsız POST, cevap kayboluyor, üç deneme.
- Varsayılanı çalıştır. Sunucuda üç sipariş var, ikisi kopya. İstemci başarı aldı. Hiçbir yerde hata logu yok.
Ağ hatası→İstek kaybolduyap. Tek sipariş. İstek sunucuya hiç ulaşmadığı için tekrar zararsız oldu.POST + Idempotency-Keyseç, cevap kaybolmaya devam etsin. Sunucu anahtarı tanıyor, işi tekrar yapmıyor. Tek sipariş.PUTveDELETEdene. Kaç kez tekrarlanırsa tekrarlansın sunucu durumu aynı.
Idempotent ile safe (güvenli) HTTP metodu arasındaki fark nedir?
İki kez gelse de bir kez yap
Idempotent: aynı isteği bir kez ya da beş kez göndermek, sunucu durumunu aynı bırakır.
İlk DELETE /orders/42 çağrısı 204 döndü, aynı çağrının tekrarı 404. Cevaplar farklı. DELETE yine de idempotent mi? Cevabı göster
Evet. Idempotency aynı cevabı değil, aynı durumu ister. İki çağrıdan sonra da durum aynıdır: sipariş 42 yok.
| Metot | Güvenli (safe) | Idempotent |
|---|---|---|
GET | Evet | Evet |
HEAD | Evet | Evet |
PUT | Hayır | Evet |
DELETE | Hayır | Evet |
POST | Hayır | Hayır |
PATCH | Hayır | Duruma göre |
PATCH ilginç olanıdır. {"total": 100} idempotenttir. {"op": "increment", "by": 10} değildir — her tekrar başka bir sonuç üretir.
Kafam karıştı, daha basit anlat
Idempotent, “aynı isteği beş kez göndersen de sonuç bir kez göndermişsin gibi” demektir. Asansör düğmesine beş kez basmak asansörü beş kez çağırmaz.
Her HTTP metodunu idempotent olup olmadığına göre yerleştir.
POST için çözüm: Idempotency-Key.
POST /ordersIdempotency-Key: 8f14e45f-ea3b-4f0c-9bc2-7a1d2e3f4a5bContent-Type: application/json
{ "items": [...] }Sunucu tarafı üç adımdır:
- Anahtarı daha önce gördün mü? Gördüysen işi yapma, sakladığın cevabı döndür.
- Görmediysen işi yap.
- Anahtarı sonucuyla birlikte sakla (bir TTL ile, örneğin 24 saat).
@PostMapping("/orders")public ResponseEntity<OrderResponse> create( @RequestHeader("Idempotency-Key") String key, @RequestBody CreateOrder request) {
return idempotencyStore.find(key) .orElseGet(() -> idempotencyStore.save(key, orderService.create(request)));}Kaydı saklarken yarış durumuna da dikkat: iki eşzamanlı istek aynı anahtarla gelebilir. Anahtar sütununa unique index koymak en basit ve en sağlam çözümdür.
Bankada bu bir havaledir: mobil uygulama “Gönder”e basıldığında bir anahtar üretir, zayıf bağlantıda aynı anahtarla tekrar dener. Aşağıdaki servis parayı yalnızca bir kez gönderir.
Derinleş · Havale: aynı anahtar, tek gönderim 5 dosya · ~94 satır · ilk okumada atlayabilirsin
Aynı `Idempotency-Key` ile iki istek geliyor. İkinci istek ne alır?
Idempotency-Key'i kim üretmelidir?
Durum kodları ve kaynak adlandırma
| Kod | Ne zaman |
|---|---|
200 OK | Başarılı GET, PUT, PATCH |
201 Created | Yeni kaynak oluştu — Location başlığı ekle |
202 Accepted | Kabul edildi, henüz işlenmedi (asenkron) |
204 No Content | Başarılı ama dönecek gövde yok — tipik DELETE |
400 Bad Request | İstek biçimsel olarak hatalı |
401 / 403 | Kimlik yok / yetki yok |
404 Not Found | Kaynak yok |
409 Conflict | Durum çakışması — örneğin aynı e-posta zaten kayıtlı |
422 Unprocessable | Biçim doğru, iş kuralı ihlal edildi |
429 Too Many Requests | Hız sınırı — Retry-After başlığı ekle |
400 ile 422 ayrımı ince ama işe yarar: 400 “bu JSON’u okuyamadım”, 422 “okudum ama bu tarih geçmişte olamaz”.
Kaynak adlandırma.
✕ GET /getUserOrders?userId=42✓ GET /users/42/orders
✕ POST /orders/42/cancel✓ POST /orders/42/cancellation (iptal bir kaynak) veya✓ PATCH /orders/42 { "status": "CANCELLED" }Kural: URL’de isim, metotta fiil. Ama dogmatik olmamak gerekir — bazı işlemler gerçekten fiildir (/orders/42/refund) ve zorlama bir kaynak icat etmek okunabilirliği düşürür.
Kafam karıştı, daha basit anlat
400, “yazdığını okuyamadım” demektir. 422 ise “okudum ama içindeki bilgi kurala uymuyor”. İkisi de istemcinin düzeltebileceği hatalardır.
`PUT /orders/42` ile `PATCH /orders/42` arasındaki fark nedir?
Sayfalama ve tuzakları
Offset (?page=3&size=20) | Cursor (?after=eyJpZCI6MTAwfQ) | |
|---|---|---|
| Uygulaması | Kolay | Daha zor |
| Rastgele sayfaya atlama | Mümkün | Hayır |
| Büyük offset performansı | Kötü — DB satırları sayar | Sabit |
| Araya kayıt eklenirse | Kayıtlar kayar, tekrar/atlama olur | Kararlı |
Sonsuz kaydırma veya büyük veri için cursor"Şu kayıttan sonrakiler" diyerek sayfalama. Offset'in aksine sabit maliyetlidir ve araya kayıt girse bile atlama yapmaz; bedeli rastgele bir sayfaya atlayamamaktır.Sözlükte gör →; yönetim panelleri gibi sayfa numarası gereken yerlerde offset.
Kendini sına
Zaman aşımı alan istemci, isteğin sunucuya ulaşıp ulaşmadığını bilemez.
Bir isteğin cevabı ağda kayboldu. İstemci için bu durum, isteğin hiç ulaşmamasından nasıl ayırt edilir?
Aklında kalacak üç şey
- 1 Idempotency aynı cevabı döndürmek değil, sunucuyu aynı durumda bırakmaktır. İkinci DELETE 404 dönebilir ve yine de idempotenttir.
- 2 Tehlikeli durum şu: cevap kaybolduğunda sunucu işi yapmıştır ama istemci bilmez. İstemci için kaybolan istek ile kaybolan cevap aynı görünür.
- 3 POST için çözüm: istemci bir Idempotency-Key üretir, sunucu anahtarı sonucuyla saklar ve aynı anahtar gelirse işi yapmadan aynı cevabı döner.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- System Design & Dağıtık SistemlerRate Limiting — Kapıdan Saniyede Kaç Kişi Geçer?Sınırı 10 saniyede 10 istek koydun. Biri iki saniyede 20 istek geçirebilir mi?Derse git
- System Design & Dağıtık SistemlerYük Dengeleme — Trafik Artınca Kapıya Kim Bakar?Üç sunucudan biri yarı hızda çalışıyor ama ayakta. Trafik ona gitmeye devam eder mi?Derse git