Idempotency Key Tasarımı: Güvenli Tekrarlanan İstekler
Dağıtık sistemlerde, ağ hataları, zaman aşımları veya istemci tarafındaki yeniden denemeler (retry) nedeniyle aynı istek birden fazla kez gönderilebilir. Özellikle para transferi, sipariş oluşturma gibi yan etkili (non-idempotent) işlemlerde bu durum, veri bozukluğuna ve istenmeyen sonuçlara (ör. çift ödeme) yol açabilir. Idempotency Key, bu sorunu çözmek için kullanılan en yaygın ve etkili desendir. İstemci, her mantıksal işlem için benzersiz bir anahtar (genellikle UUID v4) üretir ve bunu istekle birlikte gönderir. Sunucu, bu anahtarı kullanarak isteğin daha önce işlenip işlenmediğini tespit eder.
1. Temel Kavram ve Çalışma Prensibi
Idempotency Key, "en az bir kez" (at-least-once) teslimat garantisi veren sistemlerde, "etkili olarak tam olarak bir kez" (effectively exactly-once) işlem yapılmasını sağlar. Çalışma prensibi şu adımlardan oluşur:
-
İstemci Anahtar Üretir: İstemci, her yeni işlem için benzersiz bir anahtar (ör. UUID) oluşturur.
-
İstek Gönderilir: İstemci, bu anahtarı genellikle
Idempotency-KeyHTTP başlığında sunucuya gönderir. -
Sunucu Kontrol Eder: Sunucu, gelen anahtarı bir depoda (veritabanı, Redis) arar.
-
İlk İstek: Anahtar bulunamazsa, sunucu isteği işler, sonucu (yanıt ve durum kodu) bu anahtarla birlikte depolar ve istemciye döner.
-
Tekrarlanan İstek: Aynı anahtarla gelen tekrarlanan istekte, sunucu depolanan yanıtı doğrudan döndürür ve işlemi tekrarlamaz.
2. İdempotency Key Tasarımında Kritik Kararlar
A. Kapsam (Scope) ve Benzersizlik
Bir idempotency key, müşteri (tenant) ve uç nokta (endpoint) bazında benzersiz olmalıdır. Aynı anahtarın farklı kullanıcılar veya farklı API'ler tarafından kullanılması, veri sızıntısına ve güvenlik risklerine yol açabilir. Bu nedenle, depolama anahtarı genellikle (user_id, idempotency_key, endpoint) veya (api_key, idempotency_key) şeklinde oluşturulur.
B. Anahtar Üretimi (Key Generation)
-
UUID v4: En yaygın ve önerilen yöntemdir. İstemci tarafında oluşturulur ve çakışma olasılığı ihmal edilebilir düzeydedir.
-
İstek Hash'i (Request Hashing): İstek gövdesinin (body) hash'lenmesiyle oluşturulur. Bu yöntem, istemcinin anahtar üretmesini gerektirmez ancak aynı isteğin farklı zamanlarda aynı hash'i üretmesi gerekir. İstekte zaman damgası (timestamp) gibi değişkenler varsa bu yöntem başarısız olur.
-
İşlem Kimliği (Transaction ID): İş mantığında zaten var olan benzersiz bir kimlik (ör. sipariş numarası) kullanılabilir.
C. Yaşam Süresi (TTL - Time-to-Live)
Idempotency key'ler sonsuza kadar saklanmamalıdır. Aksi takdirde, depolama alanı gereksiz yere büyür. Tipik bir TTL aralığı 24-72 saat arasındadır. TTL süresi, işlemin doğasına göre belirlenmelidir:
-
Kısa TTL (dakikalar): Oturum bazlı veya geçici işlemler.
-
Uzun TTL (günler): Fatura, ödeme gibi uzun vadeli işlemler.
D. "First-Wins" ve "İşlemde" (In-Progress) Durumu
Eşzamanlı isteklerde (aynı key ile aynı anda gelen iki istek), "ilk kazanan" (first-wins) yaklaşımı benimsenmelidir. İlk istek işleme alınırken, aynı anahtarla gelen diğer istekler ya kilitlenmeli (lock) ya da "işlemde" (processing) yanıtı dönmelidir. Bu, aynı işlemin iki kez başlatılmasını engeller.
3. .NET'te Idempotency Key Uygulama Stratejileri
.NET ekosisteminde idempotency key uygulamak için çeşitli yaklaşımlar mevcuttur:
A. Özel Middleware ile Uygulama (ASP.NET Core)
En esnek yöntem, özel bir middleware oluşturmaktır. Bu middleware, gelen istekleri yakalar, Idempotency-Key başlığını okur, bir depoda (Redis, veritabanı) kontrol eder ve gerekli işlemleri yapar.
csharp
// Idempotency Middleware taslağı
public class IdempotencyMiddleware
{
private readonly RequestDelegate _next;
private readonly IIdempotencyStore _store;
public async Task InvokeAsync(HttpContext context)
{
if (!context.Request.Headers.TryGetValue("Idempotency-Key", out var key))
{
await _next(context);
return;
}
var idempotencyKey = key.ToString();
var cacheKey = $"{context.User.Identity?.Name}:{idempotencyKey}";
// 1. Cache'te kontrol et
var cachedResponse = await _store.GetAsync(cacheKey);
if (cachedResponse != null)
{
// 2. Önbellekten yanıtı döndür
await WriteCachedResponseAsync(context, cachedResponse);
return;
}
// 3. İşlemi gerçekleştir ve sonucu cache'le
// ... (isteği işle, response'u yakala)
await _store.SetAsync(cacheKey, response, TimeSpan.FromHours(24));
await _next(context);
}
}
B. MVFC.Idempotence Kütüphanesi (Redis Tabanlı)
Hafif ve verimli, Redis ile desteklenen bir idempotency kütüphanesidir. ASP.NET Core Minimal API'ler için akıcı (fluent) endpoint filtreleri sunar.
csharp
// Program.cs
builder.Services.AddIdempotency(options =>
{
options.ConfigureRedis(redisConnectionString);
options.KeyResolver = context => context.Request.Headers["Idempotency-Key"];
options.Expiration = TimeSpan.FromHours(24);
});
C. İdempotent Consumer Pattern (Mesaj Kuyrukları için)
Mesaj kuyrukları (RabbitMQ, Kafka) ile çalışırken, idempotency key yerine Inbox Pattern (Gelen Kutusu Deseni) kullanılır. Her mesajın benzersiz bir kimliği (message-id) vardır. Tüketici (consumer), bu kimliği bir veritabanında saklayarak aynı mesajı iki kez işlemez.
D. Hata Yönetimi ve Başarısız İstekler
Stripe'ın uygulamasında olduğu gibi, başarısız olan (hatalı yanıt dönen) ilk isteklerin sonucu da idempotency key ile birlikte saklanır. Bu sayede, aynı anahtarla gelen sonraki isteklerde, sunucu işlemi tekrarlamaz ve aynı hata yanıtını döndürür.
4. En İyi Pratikler (Best Practices)
-
Anahtarı İstemci Üretsin: Sunucu, istemcinin ürettiği anahtarı doğrulamalı ancak kendisi üretmemelidir.
-
Kapsamı Doğru Belirleyin: Anahtarı
(kullanıcı, endpoint)bazında benzersiz yapın. -
TTL'yi Akıllıca Ayarlayın: İşlemin doğasına göre TTL belirleyin. 24 saat genel bir başlangıç noktasıdır.
-
"First-Wins" Yaklaşımını Kullanın: Eşzamanlı istekleri yönetmek için kilit veya işlemde (processing) durumu kullanın.
-
Başarısız İstekleri de Önbelleğe Alın: Hata yanıtlarını da saklayarak aynı hatanın tekrarlanmasını engelleyin.
-
Anahtar Boyutunu Sınırlayın: Stripe, anahtar uzunluğunu 255 karakterle sınırlandırmaktadır.
-
Doğal Anahtarları (PUT/PATCH) Tercih Edin: Kaynak güncellemelerinde (PUT/PATCH), özel idempotency key yerine kaynak URI'si ve ETag kullanmayı değerlendirin.
Sonuç
Idempotency Key, dağıtık sistemlerde veri tutarlılığını sağlamak için kritik bir araçtır. Özellikle finansal işlemler, sipariş yönetimi ve ödeme sistemlerinde, isteklerin güvenli bir şekilde yeniden denenmesini (retry) mümkün kılar. .NET ekosisteminde, özel middleware'ler, Redis tabanlı kütüphaneler (MVFC.Idempotence) ve mesaj kuyrukları için Inbox Pattern ile etkili implementasyonlar yapılabilir. Doğru key yönetimi, TTL politikası ve scope tasarımı, idempotency sisteminin başarısının anahtarıdır.