Ağ Yeniden Deneme Faciası
Smart Tech Devs’te finansal işlemleri işleyen bir B2B SaaS platformu geliştirirken, mükemmel ağ koşullarına güvenemezsiniz. Bir istemcinin POST /api/invoices/123/pay isteği gönderdiğini hayal edin. Laravel sunucunuz isteği alır, Stripe ile kredi kartını tahsil eder ve geri 200 OK yanıtını gönderir. Ama yanıt istemciye ulaşmadan önce, Wi-Fi bağlantısı kopar.
İstemcinin uygulaması isteğin başarısız olduğunu varsayar ve otomatik olarak yeniden dener. Sunucunuz aynı yükü tekrar alır, işlemi gerçekleştirir ve müşteriden ikinci kez ücret alır. Artık öfkeli bir müşteri, bir geri ödeme anlaşmazlığı ve büyük bir mimari yükümlülüğünüz var. Bunu çözmek için, mutasyon uç noktalarınız İdempotent olmalıdır.
Kurumsal Çözüm: İdempotens Anahtarları
İdempotens, birden fazla aynı isteğin, tek bir istekle aynı etkiye sahip olmasını garanti eder. Bunu, istemcinin her POST veya PATCH isteği ile benzersiz bir Idempotency-Key başlığı (genellikle bir UUID) göndermesini isteyerek başarırız.
Laravel isteği aldığında, Redis’i kontrol eder. Anahtar yoksa, ödemeyi işler ve HTTP yanıtını bu anahtarla Redis’e kaydeder. İstemci aynı anahtarla isteği tekrar denerse, controller’ı tamamen atlayarak yalnızca önbelleğe alınmış HTTP yanıtını döneriz.
İdempotensi Middleware Tasarımı
Bunu yalnızca middleware katmanında uygularız, böylece controller’larımız temiz kalır ve önbellekleme mekanizmalarından habersiz olur.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class RequireIdempotencyKey
{
public function handle(Request $request, Closure $next)
{
// Yalnızca mutasyon isteklerinde idempotensi zorunlu kıl
if ($request->isMethodSafe()) {
return $next($request);
}
$key = $request->header('Idempotency-Key');
if (!$key) {
return response()->json(['error' => 'İdempotensi Anahtarı başlığı gereklidir.'], 400);
}
// Çakışmaları önlemek için önbellek anahtarını kimlik doğrulanmış kullanıcıya göre sınırlandırıyoruz
$cacheKey = "idempotency:{$request->user()->id}:{$key}";
// 1. Bu anahtar için zaten bir yanıt olup olmadığını kontrol et
if (Cache::has($cacheKey)) {
$cachedResponse = Cache::get($cacheKey);
// Önbellekteki Laravel yanıtını yeniden oluştur
return response($cachedResponse['content'], $cachedResponse['status'])
->withHeaders($cachedResponse['headers']);
}
// 2. Hızlı ardışık istekleri önlemek için anahtarı kilitle (Race Conditions)
$lock = Cache::lock("idempotency_lock:{$cacheKey}", 10);
if (!$lock->get()) {
return response()->json(['error' => 'İstek zaten işleniyor.'], 409);
}
try {
// 3. Gerçek controller mantığını işle
$response = $next($request);
// 4. Gerçek HTTP yanıtını 24 saat boyunca önbelleğe al
if ($response->isSuccessful()) {
Cache::put($cacheKey, [
'content' => $response->getContent(),
'status' => $response->getStatusCode(),
'headers' => $response->headers->all(),
], now()->addHours(24));
}
return $response;
} finally {
$lock->release();
}
}
}
Mühendislik ROI’si
Temel uç noktalarınıza bir İdempotensi Middleware uygulayarak, API’lerinizin tam olarak Stripe’ın dünya standartlarındaki mimarisi gibi davranmasını sağlarsınız. Kazara çift ücretlendirme riskini tamamen ortadan kaldırır, veritabanınızı hızlı tekrar denemelerden korur ve dış geliştiricilere API’nizin aşırı ağ dalgalanmalarında güvenli olduğunu gösterirsiniz.
Kaynak: Orijinal Makale


