هذا المقال للمستوى المتوسط — بيفترض إنك بتشتغل على REST APIs وفاهم HTTP methods الأساسية، لكن مش لازم تكون اشتغلت على أنظمة دفع قبل كده.
Idempotency Keys: العلاج لمشكلة الدفع المزدوج في APIs
لو عميل ضغط زر "ادفع 500 جنيه" والـ request اتبعت، الإنترنت قطع ثانيتين، التطبيق ما استلمش الرد، فحاول تاني تلقائيًا — وفجأة الفاتورة بقت 1000 جنيه. ده مش bug في كود الدفع. ده غياب لـ Idempotency Key. في الـ 9 دقايق الجاية هتفهم المشكلة دي بالظبط، وهتطلع بكود FastAPI شغّال يحلها.
المشكلة باختصار
الـ network مش موثوق بشكل مطلق. لما عميل بيبعت request HTTP لبوابة دفع، فيه 4 نقاط فشل ممكنة:
- الـ request ما وصلش للسيرفر أصلاً.
- الـ request وصل، السيرفر نفّذ، الرد ضاع في الرجوع.
- الـ request وصل، السيرفر بدأ ينفّذ، crash قبل ما يخلّص.
- الـ request وصل والرد رجع، لكن العميل أخد timeout قبل ما يستلمه.
في الـ 4 حالات، العميل مش عارف لو العملية اتنفّذت ولا لأ. لو الـ HTTP client بيعمل retry تلقائي (وده الـ best practice في كل client حديث زي axios و requests)، بنشوف الدفع المزدوج.
مثال للمبتدئ: ساعي البريد المسجّل
تخيّل إنك بتبعت طرد بـ 500 جنيه قيمة لشخص في محافظة تانية. بعت الطرد ومعاه رقم تتبّع فريد، يعني سلسلة أرقام مفيش طرد تاني ليه نفسها. وقت ما الساعي وصل، المستلم وقّع وكتب رقم التتبّع في دفتر الاستلام. بعد يومين، الساعي رجع تاني ومعاه طرد جاي من نفس المرسل بنفس قيمة 500 جنيه. المستلم ما بيستلمش بناءً على القيمة، بيستلم بناءً على رقم التتبّع. لو الرقم اللي مع الساعي ده مكتوب قبل كده، يرفض الاستلام ويرجّع الطرد.
Idempotency Key بيشتغل بنفس المنطق. العميل بيولّد رقم فريد لكل عملية دفع وبيرفقه مع الـ request. السيرفر بيشوف الرقم: لو موجود في سجلاته، يرجّع نفس النتيجة الأصلية بدون تنفيذ. لو مش موجود، ينفّذ العملية ويسجّل النتيجة مربوطة بالرقم.
التعريف العلمي
عملية idempotent هي عملية لو نفّذتها مرة أو نفّذتها N مرة، تأثيرها على حالة النظام واحد. حسب RFC 9110، الفقرة 9.2.2، الـ HTTP methods التالية بطبيعتها idempotent: GET، HEAD، PUT، DELETE، OPTIONS، TRACE. POST بطبيعته مش idempotent، لأن المتوقّع منه إنه يخلق resource جديد كل مرة.
Idempotency Key بيحوّل POST لـ idempotent بإضافة طبقة حالة على السيرفر تربط مفتاح فريد بنتيجة العملية. الـ IETF Draft الخاص بـ Idempotency-Key Header بيوصي بـ UUID v4 (128 بت، احتمال التكرار عمليًا صفر) كقيمة للـ header. شركات زي Stripe و PayPal و Square كلها بتطبّق نفس النمط بنفس اسم الـ header.
التطبيق العملي على FastAPI
هنبني endpoint دفع مبسّط، نخزّن مفاتيح Idempotency في Redis مع TTL 24 ساعة، ونمنع تنفيذ نفس العملية مرتين حتى لو وصلت في نفس المللي ثانية.