Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Idempotency ve eşzamanlılık

Bu sayfa, ağ koptuğunda bir isteği iki kez uygulamadan nasıl tekrar edeceğinizi (idempotency, yani tekrar güvenliği) ve iki istemcinin aynı kaynağı aynı anda değiştirmesini iyimser sürüm denetimiyle (optimistic locking) nasıl yöneteceğinizi anlatır. Para, eşya ya da ödül veren her çağrıda bu iki kuralı kullanın.

Idempotency-Key

Değiştiren bir istek (POST, bazı PUT/PATCH/DELETE) Idempotency-Key başlığı taşıyabilir ya da taşımak zorundadır. Anahtarın kuralları:

KuralDeğer
Uzunluk1 ile 200 karakter (boş olamaz)
Karakter kümesiBoşluksuz yazdırılabilir ASCII (SDK yalnız bunu üretir ve kabul eder)
Önerilen değerRastgele UUID (v4); bazı uçlar UUID biçimini zorunlu kılar (klan, grup ve turnuva uçları: geçerli UUID olmalıdır)
KapsamÇekirdek komutlarda anahtar ortam genelinde benzersizdir: aynı ortamda iki farklı çağıran aynı anahtarı kullanırsa çakışır. Eklenti komutlarında anahtar eklenti ve çağıran (oyuncu ya da sunucu) başınadır. Bu yüzden her zaman rastgele UUID kullanın, oyuncu-1 gibi tahmin edilebilir değerler kullanmayın
Kayıt süresi24 saat (komut kayıtları); eklenti komutlarında işlemin receiptRetentionSeconds değeri (şimdilik 86 400 sn)

Hangi uçta anahtarın zorunlu, hangisinde isteğe bağlı olduğunu uç nokta referansı gösterir. Örnek: karakter komutlarında isteğe bağlıdır, cüzdan aktarımında, envanter ve eşya işlemlerinde, ticarette, mağaza ve eklenti komutlarında zorunludur.

Anahtar nasıl çalışır

  1. İlk istek anahtarla gelir; sunucu işlemi uygular ve sonucu anahtara bağlı saklar.
  2. Aynı anahtar + aynı içerik tekrar gelirse işlem yeniden uygulanmaz; kayıtlı sonuç döner. Bazı yanıtlar bunu replayed: true alanıyla bildirir (karakter komutları, mağaza satın alma).
  3. Aynı anahtar + farklı içerik 422 ve idempotency mismatch (eklentide idempotency_mismatch) alır.
  4. İlk istek hâlâ işleniyorsa ikincisi 409 (idempotent request in progress ya da idempotency_in_flight) alır; kısa süre sonra aynı anahtarla tekrar deneyin.
  5. Kayıt süresi dolduysa 410 döner.
  6. Çekirdek komutlarda anahtar yalnız başarıda “yanar”: yetki reddi (ör. istemci anahtarıyla sunucu komutu), doğrulama hatası ya da geri alınan işlem anahtarı tüketmez; düzeltip aynı anahtarla tekrar gönderebilirsiniz.

Uyarı

Belirsiz sonuçlu bir isteği yeni anahtarla tekrarlamayın. Yeni anahtar yeni işlem demektir ve çift ödül, çift harcama yaratır. Anahtarı bir kez üretin, saklayın (istek ve yeniden denemeler boyunca aynı kalsın) ve gerekirse sonucu sorgulayın.

Ne zaman yeni anahtar üretilir

  • Kullanıcı aynı işlemi bilerek yeniden başlattığında (ör. ikinci mağaza satın alması).
  • İş kuralı reddinden (422) sonra durumu değiştirip yeniden denediğinizde.
  • 410 (anahtar süresi doldu) sonrasında, durumu okuyup işlemin gerçekten uygulanmadığından emin olduktan sonra.

Örnek: güvenli yeniden deneme

Aşağıdaki yardımcı anahtarı bir kez üretir, ağ hatalarında ve 408, 429, 5xx yanıtlarında aynı anahtarla yeniden dener ve Retry-After süresini bekler:

/** `Retry-After` değerini milisaniyeye çevirir; saniye ya da HTTP tarihi olabilir, bilinmiyorsa `null`. */
function retryAfterMs(header: string | null): number | null {
  if (header === null) return null;
  if (/^\d+$/.test(header)) return Number(header) * 1000;
  const date = Date.parse(header);
  return Number.isNaN(date) ? null : Math.max(0, date - Date.now());
}

const RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);

/**
 * Bir değiştirici isteği AYNI `Idempotency-Key` ile güvenle yeniden dener.
 * Anahtarı bir kez üretin ve her denemede aynen gönderin; yeni anahtar yeni işlem demektir.
 * 429 ve 503'te sunucunun `Retry-After` süresini bekler; yoksa üstel geri çekilme kullanır.
 */
export async function postWithRetry(url: string, body: unknown, headers: Record<string, string>, maxAttempts = 4): Promise<Response> {
  const idempotencyKey = crypto.randomUUID();
  let lastError: unknown;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    let response: Response | undefined;
    try {
      response = await fetch(url, {
        method: "POST",
        credentials: "include",
        headers: { ...headers, "content-type": "application/json", "idempotency-key": idempotencyKey },
        body: JSON.stringify(body),
      });
    } catch (error) {
      lastError = error; // ağ hatası: istek uygulanmış olabilir, aynı anahtarla tekrar güvenlidir
    }
    if (response !== undefined) {
      if (!RETRYABLE_STATUS.has(response.status)) return response; // başarı ya da kalıcı hata
      lastError = new Error(`HTTP ${response.status}`);
      if (attempt === maxAttempts) return response;
      const wait = retryAfterMs(response.headers.get("retry-after")) ?? Math.min(200 * 2 ** (attempt - 1), 5000);
      await new Promise((resolve) => setTimeout(resolve, wait));
      continue;
    }
    if (attempt < maxAttempts) await new Promise((resolve) => setTimeout(resolve, Math.min(200 * 2 ** (attempt - 1), 5000)));
  }
  throw lastError;
}

TypeScript SDK’sı bunu sizin yerinize yapar: CrxClient (sunucu anahtarı) GET isteklerini ve Idempotency-Key taşıyan istekleri 408, 429, 500, 502, 503, 504 durumlarında (varsayılan 2 deneme, maxRetries ile 0 ile 5 arası) üstel geri çekilmeyle ve Retry-After’a saygı göstererek yeniden dener. Eklenti komutlarında PlayerClient anahtarı hazırlama anında üretir ve tarayıcı günlüğüne (journal) yazar; sayfa yenilense bile aynı anahtarla devam eder (bağlamalar).

Sonucu sorgulama (eklenti komutları)

Eklenti komutu için yanıtı hiç alamadıysanız, tekrar göndermeden GET …/commands/{message_type}/result?idempotency_key=… ile sonucu sorgulayın. Yalnız 404 (kayıt yok) yanıtında aynı anahtarla yeniden gönderin (eklentileri çağırma). Çekirdek komutlarda ayrı bir sorgu ucu yoktur; aynı anahtar ve gövdeyle yeniden göndermek aynı kayıtlı sonucu döndürür.

İyimser sürüm denetimi

Sunucu, değiştirilebilir kaynaklara (karakter, envanter, eklenti kaynağı, mağaza ilanı) artan bir sürüm (version) tutar. İsteğinizde “şu sürümü görüp karar verdim” derseniz ve bu arada başkası kaynağı değiştirdiyse, istek uygulanmaz ve çakışma döner. Böylece iki istemci birbirinin değişikliğini sessizce ezmez.

Kullanım yeriNasıl gönderilir
Karakter komutları (POST /v1/characters/{id}/commands)If-Match: 3 (tırnaklı "3" de kabul edilir)
Envanter ve eşya işlemleri (If-Match destekleyen uçlar)If-Match
Mağaza yönetim uçları (sayfa, ilan değişiklikleri)If-Match (isteğe bağlı)
Eklenti komutları (HTTP)If-Match: "12"
Eklenti komutları (realtime Command çerçevesi)expectedVersion: "12" (ondalık metin)
Oyuncu satın alma (/v1/player/store/listings/{id}/buy)If-Match kullanılmaz; ilan sürümü gövdedeki expected_version alanıdır (başlık gönderirseniz 400)

If-Match biçimi: tam sayı sürüm numarası. * ve zayıf doğrulayıcılar (W/"3") reddedilir (400); negatif sayı ve sayısal olmayan değer 400 alır. Başlık verilmezse sürüm denetimi yapılmaz (son yazan kazanır); para ve eşya gibi kritik kaynaklarda göndermenizi öneririz.

Çakışma gelince

Uç ailesiYanıt
Çekirdek REST409 ve version conflict: expected 3, actual 5
Eklenti409 ve {"code":"conflict","message":"version conflict: expected 3, actual 5",…}
Mağaza oyuncu satın alma409 ve {"code":"listing_changed",…}
Realtime komutuError çerçevesi InvalidPayload (retryable: false); ayrıntı telden verilmez

Çözüm döngüsü:

  1. Kaynağı yeniden okuyun (güncel version ile).
  2. Kullanıcının niyeti hâlâ geçerli mi karar verin (ör. hâlâ yeterli bakiye var mı).
  3. Yeni anahtarla (bu yeni bir deneme) güncel sürümle tekrar gönderin.

Not: sürüm çakışması iş kuralı değil, eşzamanlılık sonucudur; aynı isteği körlemesine tekrar göndermeniz yine çakışır. Idempotency anahtarı tekrar içindir, sürüm denetimi çakışma içindir; ikisini birlikte kullanabilirsiniz: aynı anahtar + aynı If-Match ile gönderilen tekrar kayıtlı sonucu döner.

Özet kural listesi

  • Para, eşya, ödül ve komut gönderen her isteğe anahtar ekleyin; anahtarı bir kez üretin, yeniden denemelerde değiştirmeyin.
  • Belirsiz sonucu sorgulayın ya da aynı anahtarla tekrarlayın; yeni anahtarla asla.
  • Paylaşılan kaynak yazarken If-Match/expectedVersion gönderin; 409’da yeniden okuyun.
  • 422 iş kuralı reddidir; tekrar etmek sonuç değiştirmez.
  • Ayrıntılı hata tablosu için hata referansı.