Idempotency Keys: امنع تكرار عمليات الدفع في الـ API
مستوى المقال: متوسط — يفترض إنك بتكتب أو تصمّم REST APIs، وعارف الفرق بين POST و PUT، وفاتحت Postgres قبل كده ولو مرة.
لو عميلك ضغط زرّ "ادفع" مرّتين بسبب بطء النت، ممكن البنك يخصم منه نفس المبلغ مرّتين. Idempotency Key بيخلّي السيرفر يرفض النسخة الثانية ويرجّع نتيجة الأولى — بسطر واحد على قاعدة البيانات. هتشوف هنا الفكرة بمثال واضح، الكود الفعلي، والأرقام بعد التطبيق.
المشكلة باختصار: السيناريو اللي بيحصل فعلاً
تخيّل سارة فتحت متجر إلكتروني، اختارت أوردر بـ 480 جنيه، وضغطت زرّ "ادفع". الموبايل اشتغل ببطء، الزرّ اتعلّق ثانيتين، فضغطت تاني علشان تتأكد. الـ frontend بعت طلبَيْن لـ /api/payments. السيرفر استلم الاتنين، والبنك خصم منها 480 × 2 = 960 جنيه. سارة دلوقتي بتعمل شكوى، وفريق الدعم بيلف 40 دقيقة في عملية اللي المفروض كانت ثانية واحدة.
المشكلة مش في سارة، ولا في الـ frontend اللي ما عملش disable للزرّ. المشكلة الجذرية: الـ endpoint مش idempotent — يعني نفس الطلب لمّا بيوصله مرّتين بيغيّر الحالة مرّتين، بدل ما يثبّت على نتيجة واحدة.
تعريف بسيط بمثال قبل ما ندخل في التفاصيل العلمية
تخيّل إنك بتضغط زرّ المصعد. ضغطت مرة، اضاء. ضغطت مرة تانية وتالتة وعشرة، نفس النتيجة — المصعد جاي. زرّ المصعد idempotent: لا يهم كم مرة تضغط، الناتج واحد.
بالعكس، زرّ "ابعت رسالة" في تطبيق شات لو ضغطته 10 مرات بيبعت 10 رسائل. ده non-idempotent. الـ APIs اللي بتنشئ موارد أو بتحرّك فلوس عادةً non-idempotent بطبيعتها — وعلشان كده بنحتاج خدعة اسمها Idempotency Key نخلّيها تتصرّف زي زرّ المصعد.
التعريف الدقيق علميًا
كلمة "Idempotent" أصلها من الرياضيات: عملية لو نفّذتها مرة أو ألف مرة بنفس المدخل بتدّي نفس النتيجة. بالصيغة الرياضية: f(f(x)) = f(x).
في HTTP، الـ methods GET و PUT و DELETE idempotent بحكم تعريف RFC 9110. لكن POST غالبًا لأ — لأنه بيُستخدم لإنشاء موارد جديدة. هنا بييجي دور Idempotency Key:
Idempotency Key هو معرّف فريد (عادةً UUID v4) يولّده الـ client ويبعته في header مع كل طلب يكتب أو يغيّر حالة. السيرفر بيخزّن الـ key + نتيجة أول تنفيذ ناجح في جدول مخصّص. لو وصله نفس الـ key تاني، بيرجّع النتيجة المحفوظة بدون ما يعيد التنفيذ.
الـ Workflow بخطوات
- الـ client بيولّد UUID جديد لكل عملية كتابة (دفع، إنشاء طلب، إرسال إيميل تأكيد).
- بيرسله في header:
Idempotency-Key: 8f3a4b2c-...-9d1e - السيرفر يدوّر على الـ key في جدول
idempotency_keys. - لو الـ key موجود + النتيجة مكتملة → يرجّع نفس الاستجابة بنفس الـ status code.
- لو موجود + لسه قيد التنفيذ → يرجّع أو .