İçeriğe geç
Muhammet Şafak
en
Web Geliştirme 3 dk okuma

API'de idempotency: aynı isteği güvenle tekrar etmek

Tekrarlanan API isteklerinin yan etki üretmemesini tasarlamak; idempotency anahtarları ve uygulama katmanında pratik çözümler.

Kapak görseli — sunucu kasasında IDEMPOTENCY yazan mor ışıklı yuvaya sokulmuş metal kart; üzerinde KEY: X5F-912-Q8J kazıması

Şöyle bir senaryo düşünün: kullanıcı bir ödeme başlatıyor, istek yolda kayboluyor ya da istemci bir timeout alıyor. İstemci ne yapacak? Yeniden denemeli mi? Ödeme gerçekleşti mi gerçekleşmedi mi bilmiyor.

Bu belirsizlik, ağ üzerinden çalışan sistemlerin kaçınılmaz bir özelliğidir. İstek gönderildi ama yanıt alınamadı — bu, “işlem yapılmadı” anlamına gelmiyor. Belki işlem tamamlandı, yanıt dönmedi. Belki işlem yarıda kaldı. Belki hiç başlamadı.

Çözüm bu belirsizliği kabul edip, tekrarlanan isteğin güvenli olmasını tasarlamak. Bu kavrama idempotency (eşgüçlülük) deniyor.

Idempotency nedir?

Matematikte bir operasyon idempotent ise, aynı girdi ile kaç kez uygulanırsa uygulansın sonuç değişmez. API bağlamında: aynı isteği birden fazla göndermek, bir kez göndermekle aynı etkiyi yaratmalıdır.

Tanım HTTP’nin kendi spesifikasyonunda duruyor. RFC 9110’un ifadesiyle bir yöntem, “aynı isteğin birden çok kez gönderilmesinin sunucu üzerinde amaçlanan etkisi tek bir isteğinkiyle aynıysa” idempotenttir; spesifikasyonun tanımladığı yöntemler arasında PUT, DELETE ve güvenli (safe) yöntemler idempotenttir. HTTP yöntemleri bu açıdan farklı davranır:

  • GET, HEAD, OPTIONS, PUT, DELETE — doğası gereği idempotent. Aynı GET /users/5 isteğini on kez gönderin; sonuç aynı.
  • POST — doğası gereği idempotent değil. Her POST /payments yeni bir ödeme oluşturabilir.

Ayrım pratikte şu yüzden önemli: RFC, idempotent bir yöntemin “istemci yanıtı okuyamadan bağlantı koptuğunda otomatik olarak tekrarlanabileceğini” söylüyor, ama idempotent olmayan bir yöntem için istemcinin bunu kendiliğinden yapmaması gerektiğini ekliyor. Yani POST’ta yeniden denemeyi güvenli kılmak bizim işimiz.

Sorun genellikle POST ile ve durumu değiştiren işlemlerde ortaya çıkıyor.

Idempotency anahtarı

Çözümün özü şu: istemci, her benzersiz operasyon için bir key üretir ve bunu istekle birlikte gönderir. Sunucu bu anahtarı gördüğünde şunu yapar: “Bu anahtarla daha önce bir istek işledim mi? Evet ise, aynı yanıtı dönerim; hayır ise, işlemi yaparım ve sonucu bu anahtarla saklarım.”

İstemci aynı anahtarla yeniden denerse, işlem tekrar çalışmaz; daha önce üretilen yanıt döner.

POST /api/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "amount": 9900,
  "currency": "TRY",
  "method": "card"
}

Bu kalıbın en bilinen uygulaması Stripe. Kendi dokümanına göre anahtarı istemci üretiyor, Stripe anahtar için V4 UUID öneriyor ve anahtarlar en az 24 saat yaşlandıktan sonra sistemden silinebiliyor. Aynı yaklaşımı kendi API’lerinizde de uygulayabilirsiniz.

Laravel’de uygulama

Temel akış şu: istek geldiğinde önce önbellekte (veya veritabanında) bu anahtara karşılık bir kayıt var mı kontrol et; varsa saklı yanıtı dön; yoksa işlemi çalıştır, yanıtı sakla ve dön.

// app/Http/Middleware/IdempotencyMiddleware.php
class IdempotencyMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $key = $request->header('Idempotency-Key');

        if (!$key) {
            return $next($request);
        }

        $cacheKey = 'idempotency:' . auth()->id() . ':' . $key;

        if ($cached = Cache::get($cacheKey)) {
            return response()->json(
                json_decode($cached['body'], true),
                $cached['status']
            );
        }

        $response = $next($request);

        // 5xx dışındaki yanıtları sakla — 4xx dahil
        if ($response->getStatusCode() < 500) {
            Cache::put($cacheKey, [
                'body'   => $response->getContent(),
                'status' => $response->getStatusCode(),
            ], now()->addHours(24));
        }

        return $response;
    }
}

Birkaç not:

Kullanıcı bazlı anahtar. auth()->id() ile anahtarı kullanıcıya bağlıyorum. Farklı kullanıcıların aynı anahtarı göndermesi durumunda çakışma olmasın.

Hata yanıtlarını saklamak. Yukarıdaki kodda 5xx saklanmıyor; istemci yeniden deneyebilmeli. 4xx (istemci hatası) ise saklanıyor: aynı hatalı isteği yeniden gönderirseniz aynı hata döner.

Bu bilinçli bir tercih ve Stripe’ınkinden farklı: Stripe ilk isteğin durum kodunu ve gövdesini, istek başarılı olsun ya da olmasın saklıyor — aynı anahtarla gelen sonraki istekler 500 hatalarını bile aynen geri alıyor. Seçim, “yeniden deneme sunucu hatasını aşabilmeli mi” sorusuna verdiğiniz cevaba bağlı. Sunucuda geçici bir arıza bekliyorsanız benim yaptığım gibi 5xx’i saklamayın; yeniden denemenin kesinlikle yeni bir yan etki üretmemesini istiyorsanız Stripe’ın davranışı daha güvenli.

Saklama süresi. 24 saat makul bir başlangıç — Stripe da anahtarları en az bir gün yaşlandıktan sonra siliyor. İş gereksinimlerine göre ayarlanabilir.

Anahtarı kim üretir?

İstemci üretmeli. Sunucu üretirse anlam kalmaz: sunucu zaten işlemi yaptı, yanıtı döndü; anahtar sonradan üretilmiş oldu. Stripe da bunu böyle tarif ediyor: anahtarı istemci üretir, sunucu onu aynı isteğin sonraki denemelerini tanımak için kullanır. İstemcinin elinde bir UUID v4 ya da benzeri benzersiz bir tanımlayıcı olmalı ve bu tanımlayıcıyı yeniden deneme boyunca korumalı.

React Native tarafında:

import { randomUUID } from "expo-crypto";

async function createPayment(amount: number) {
  const idempotencyKey = randomUUID();

  try {
    const response = await api.post(
      "/payments",
      { amount },
      { headers: { "Idempotency-Key": idempotencyKey } }
    );
    return response.data;
  } catch (error) {
    if (isNetworkError(error)) {
      // Aynı anahtarla yeniden dene
      return retryWithSameKey(idempotencyKey, amount);
    }
    throw error;
  }
}

Anahtar, ödeme nesnesi oluşturulduğunda üretilir; yeniden denemede aynı anahtar kullanılır. Sunucu ikinci isteği görünce zaten işlemi yapmış olduğunu anlayacak.

Sadece ödeme değil

Idempotency’nin değeri ödeme sistemleriyle sınırlı değil. Kullanıcı kaydı, e-posta gönderimi, stok rezervasyonu — kullanıcı için görünür bir yan etkisi olan her POST işlemi bu desenden yararlanabilir.

Özellikle mobil uygulamalarda ağ bağlantısı güvenilmez; kullanıcı “Gönder” butonuna birden fazla kez basabilir. Bu durumları ele almak için istemci tarafında çift gönderimi engellemek (buton devre dışı bırakmak) yeterli değil; sunucunun da hazır olması gerekir.

Tasarımı bu varsayım üzerine kurmak — “istek tekrar gelebilir” — daha dayanıklı bir API ortaya çıkarır.


Kaynaklar

Bu konudaki deneyler

Kaydırmalı doğru/yanlış bilgi yarışması; asıl mesele oyun değil, istemcinin ürettiği skoru imzayla doğrulanabilir kılmak.

Şu an ne yapıyor

Kaydırmalı doğru/yanlış bilgi yarışması bir mobil uygulama olarak çekirdek döngüsüyle uçtan uca çalışıyor; skoru istemci üretse de server_nonce ve HMAC imzasıyla sunucu tarafında doğrulanıyor. swipenor.com uygulamanın tanıtım sayfasıdır, oyunun kendisi değil.

Mobil uygulama Laravel PHP PostgreSQL +6 daha
Temmuz 2026 — Devam ediyor
Etiketler: #API
Paylaş:

Son güncelleme:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi