كانت الشبكة السيئة تضاعف طلبات الشراء: كيف أنقذنا مفهوم “العطالة” (Idempotency) من جحيم الكوارث المالية؟

أذكر ذلك اليوم جيداً، كان يوم ثلاثاء، وكنا قد أطلقنا للتو حملة تخفيضات كبيرة لأحد عملائنا في مجال التجارة الإلكترونية. الأجواء في المكتب كانت حماسية، نراقب لوحات التحكم والطلبات تتدفق كالنهر. وفجأة، رن هاتفي. على الطرف الآخر كان مدير المشروع من طرف العميل، صوته يرتجف من القلق: “أبو عمر الحقنا! المصاري بتروح مرتين! العملاء عم يشتكوا إنه بنخصم منهم المبلغ مرتين على نفس الطلب!”.

في تلك اللحظة، يتجمد الدم في عروقك كمبرمج. كارثة مالية تعني فقدان ثقة المستخدم، وهذا أسوأ كابوس لأي مشروع. ناديت على الفريق: “يا جماعة، وقفوا كل إشي، عنا حريقة لازم نطفيها!”. بدأنا فوراً في تحليل السجلات (Logs)، وكانت المفاجأة: وجدنا طلبات شراء متطابقة تماماً تصل إلى الخادم (Server) بفارق زمني لا يتجاوز الثواني، كلها لنفس المستخدم ونفس المنتجات. لم يكن خطأ في نظام الدفع، بل كان شيئاً أبسط وأكثر خبثاً: شبكة الإنترنت السيئة.

كان المستخدم، بسبب بطء اتصاله، يضغط على زر “إتمام الشراء” مرة تلو الأخرى، معتقداً أن الضغطة الأولى لم تعمل. ومع كل ضغطة، كان المتصفح يرسل طلب POST جديداً لإنشاء طلب شراء جديد. والنتيجة؟ فوضى عارمة وخصومات مضاعفة. هنا، لم يكن الحل في تحسين أداء الخادم، بل في تبني مفهوم برمجي أنقذنا من هذا الجحيم: مفهوم “العطالة” أو الـ Idempotency.

ما هي “العطالة” (Idempotency) يا أبو عمر؟ ببساطة شديدة

دعك من الاسم المعقد، فالمفهوم بسيط للغاية. تخيل أنك في مصعد وضغطت على زر الطابق العاشر. إذا ضغطت عليه مرة أخرى، أو عشر مرات، هل سيحدث شيء مختلف؟ بالطبع لا. النتيجة واحدة: المصعد سيتجه إلى الطابق العاشر. هذا الفعل “عطِل” أو Idempotent.

في المقابل، تخيل أنك تسحب المال من صراف آلي. كل عملية سحب هي عملية فريدة ولها تأثيرها الخاص. سحب 100 دولار مرة ليس كـسحبها مرتين. هذا الفعل “غير عاطل” أو Non-idempotent.

في عالم الواجهات البرمجية (APIs)، الطلب “العاطل” هو الطلب الذي يمكنك إرساله عدة مرات متتالية، لكن النتيجة على الخادم ستكون هي نفسها كما لو أنك أرسلته مرة واحدة فقط. التأثير يحدث مرة واحدة فقط، مهما حاولت.

لماذا تعتبر العطالة شريان الحياة في تصميم الـ APIs؟

قد تعتقد أن هذا مجرد تفصيل تقني، لكنه في الواقع حجر أساس في بناء أنظمة قوية وموثوقة. وإليك الأسباب:

1. الشبكات مش دايماً منيحة (الواقع المرير)

الشبكات غير موثوقة بطبيعتها. يمكن أن ينقطع الاتصال، أو يحدث تأخير (timeout)، أو قد يقوم المستخدم، كما في قصتنا، بإعادة المحاولة بنفسه. بدون العطالة، كل محاولة إعادة إرسال قد تؤدي إلى تكرار العملية (مثل إنشاء طلب شراء جديد أو خصم مبلغ مالي آخر).

2. حماية من الكوارث المالية والتجارية

كما رأينا، في تطبيقات التجارة الإلكترونية أو أي نظام يتعامل مع المال، العطالة ليست رفاهية بل ضرورة قصوى. هي تحميك من:

  • خصم مبالغ مضاعفة من العملاء.
  • إنشاء طلبات شراء أو فواتير مكررة.
  • إرسال نفس الإشعار أو البريد الإلكتروني للمستخدم عشرات المرات.

3. بناء أنظمة موزعة قوية (Robust Distributed Systems)

في معماريات الخدمات المصغرة (Microservices)، تتواصل الخدمات مع بعضها البعض عبر الشبكة. من الشائع جداً أن تقوم خدمة ما بإعادة محاولة الاتصال بخدمة أخرى إذا فشلت المحاولة الأولى. العطالة تضمن أن هذه الإعادة آمنة ولا تسبب تكرار البيانات أو العمليات.

كيف نطبق مفهوم العطالة عملياً؟ (هنا الشغل الصح)

الحديث النظري جميل، لكن كيف نطبّق هذا على أرض الواقع في الكود؟ الأمر يعتمد على نوع الطلب الذي تتعامل معه.

h3. استخدام أفعال HTTP الصحيحة (The Right HTTP Verbs)

لحسن الحظ، بعض أفعال بروتوكول HTTP مصممة لتكون “عاطلة” بطبيعتها:

  • GET: طلب جلب بيانات. يمكنك طلب بيانات مستخدم ألف مرة، ولن يتغير شيء على الخادم. (عطول عاطل).
  • PUT: طلب تحديث مورد بالكامل. إذا أرسلت طلب PUT /users/123 بنفس البيانات مرتين، ستكون حالة المستخدم 123 النهائية هي نفسها. (عاطل بطبيعته).
  • DELETE: طلب حذف مورد. بعد حذف /orders/456 بنجاح، أي طلب حذف آخر لنفس المورد لن يغير شيئاً في حالة النظام (الطلب محذوف بالفعل). (عاطل بطبيعته).

المشكلة الحقيقية تكمن في الفعل POST. هذا الفعل مصمم لإنشاء مورد جديد في كل مرة. POST /orders يعني “أنشئ لي طلباً جديداً”، وهو بالضبط ما سبب لنا الكارثة.

h3. الحل السحري: مفتاح العطالة (Idempotency-Key)

لجعل العمليات غير العاطلة (مثل POST) آمنة وقابلة للتكرار، نستخدم تقنية بسيطة وفعالة جداً تُعرف بـ “مفتاح العطالة”. وهي تعمل كالتالي:

  1. من جهة العميل (Client-Side): قبل إرسال الطلب الحساس (مثل إنشاء دفعة)، يقوم العميل بتوليد مُعرّف فريد وخاص بهذه العملية (مثلاً، UUID). ثم يرسل هذا المعرّف ضمن ترويسة (Header) الطلب، غالباً تحت اسم Idempotency-Key.
  2. من جهة الخادم (Server-Side): عندما يستقبل الخادم الطلب، يقوم بالآتي:
    • يتحقق من وجود ترويسة Idempotency-Key.
    • يبحث عن قيمة هذا المفتاح في مخزن مؤقت (مثل Redis أو قاعدة بيانات).
    • إذا كان المفتاح جديداً (لم يره من قبل): يقوم بمعالجة الطلب كالمعتاد (ينشئ الطلب، يخصم المبلغ…). بعد ذلك، وقبل إرسال الرد، يقوم بتخزين نتيجة العملية (الرد + كود الحالة) مرتبطاً بهذا المفتاح.
    • إذا كان المفتاح موجوداً (رآه من قبل): هنا يكمن السحر. الخادم لا يعيد معالجة الطلب. بدلاً من ذلك، يقوم فوراً بجلب النتيجة المخزنة مسبقاً وإرسالها مرة أخرى للعميل.

بهذه الطريقة، حتى لو أرسل العميل نفس الطلب مع نفس المفتاح مئة مرة، العملية ستُنفذ مرة واحدة فقط!

مثال بالكود (شبه كود بلغة Python لتوضيح الفكرة)


# هذا مجرد مثال توضيحي، في التطبيق الحقيقي استخدم Redis أو ما شابه
idempotency_store = {}

@app.route('/api/v1/payments', methods=['POST'])
def create_payment():
    # 1. استخلاص مفتاح العطالة من الترويسة
    idempotency_key = request.headers.get('Idempotency-Key')

    if not idempotency_key:
        return {"error": "Idempotency-Key header is required"}, 400

    # 2. التحقق مما إذا كنا قد رأينا هذا المفتاح من قبل
    if idempotency_key in idempotency_store:
        # نعم، رأيناه! أرجع الرد المحفوظ فوراً
        saved_response, saved_status_code = idempotency_store[idempotency_key]
        return jsonify(saved_response), saved_status_code

    # 3. هذا طلب جديد، لنقم بمعالجته
    try:
        # ... هنا تضع منطق إنشاء الدفعة في قاعدة البيانات ...
        # ... ومنطق التواصل مع بوابة الدفع ...
        payment_result = {"payment_id": "pay_123xyz", "status": "succeeded"}
        status_code = 201 # Created

        # 4. قبل إرسال الرد، خزّن النتيجة مع المفتاح
        # يجب أيضاً وضع مدة صلاحية (TTL) لهذا التخزين
        idempotency_store[idempotency_key] = (payment_result, status_code)

        # 5. أرسل الرد الجديد للعميل
        return jsonify(payment_result), status_code

    except Exception as e:
        # تعامل مع الأخطاء التي قد تحدث أثناء المعالجة
        return {"error": "Payment processing failed"}, 500

نصائح من خبرة أبو عمر

العطالة، يا جماعة، مثل حزام الأمان في السيارة. قد لا تحتاجه كل يوم، لكن في اليوم الذي تحتاجه فيه، سينقذ حياتك (أو مشروعك).

  • اختر المفتاح الصح: يجب أن يقوم العميل بتوليد المفتاح، ويجب أن يكون فريداً لكل عملية يريد المستخدم القيام بها، وليس لكل طلب HTTP. إذا حاول المستخدم إعادة المحاولة، يجب أن يرسل نفس المفتاح.
  • حدد مدة صلاحية (TTL): لا تخزن مفاتيح العطالة إلى الأبد. هذا سيملأ قاعدة بياناتك أو ذاكرتك المؤقتة. مدة 24 ساعة غالباً ما تكون كافية لمعظم الحالات.
  • مش كل إشي بده عطالة: لا تفرط في استخدامها. ركز على العمليات الحساسة التي لا يجب أن تتكرر (POST, وأحياناً PATCH). عمليات القراءة (GET) لا تحتاجها.
  • وثّق يا حبيبي وثّق (Document!): إذا كانت واجهتك البرمجية تتوقع Idempotency-Key، فتأكد من توضيح ذلك بشكل جلي في التوثيق الخاص بالـ API. اشرح للمطورين الآخرين كيف يولدون المفتاح ومتى يرسلونه.

الخلاصة: العطالة مش رفاهية، هي ضرورة

في النهاية، ما تعلمناه من تلك الحادثة العصيبة هو أن بناء أنظمة برمجية لا يقتصر على كتابة كود يعمل في الظروف المثالية. بل يتعلق ببناء أنظمة قادرة على الصمود في وجه فوضى العالم الحقيقي: شبكات متقطعة، مستخدمون غير صبورين، وأخطاء لا مفر منها. العطالة هي واحدة من أهم أدواتك في بناء هذه الصمود.

في المرة القادمة التي تصمم فيها API لعملية حساسة، توقف لحظة واسأل نفسك: “ماذا لو أُرسل هذا الطلب مرتين؟”. إذا كان الجواب “كارثة”، فأنت الآن تعرف ما يجب عليك فعله. ما تهملوها يا جماعة! 👍

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

برمجة وقواعد بيانات

تحديثات قاعدة البيانات بدون توقف: كيف أنقذنا نمط التوسيع والتعاقد (Expand/Contract) من جحيم التوقفات المجدولة؟

هل سئمت من إيقاف الخدمة مع كل تحديث لهيكلة قاعدة البيانات؟ أشارككم قصة حقيقية وكيف أنقذنا نمط التوسيع والتعاقد (Expand/Contract) من ليالي النشر الطويلة والمُجهدة،...

4 يونيو، 2026 قراءة المزيد
الشبكات والـ APIs

كانت إعادة المحاولة كارثة: كيف أنقذتنا مفاتيح عدم تكرار العمليات (Idempotency Keys) من جحيم الفواتير المزدوجة؟

أشارككم قصة حقيقية من الخنادق البرمجية، يوم كاد خطأ بسيط في إعادة محاولة طلبات الدفع أن يكلفنا سمعتنا وأموال عملائنا. اكتشفوا معنا كيف كانت مفاتيح...

4 يونيو، 2026 قراءة المزيد
الحوسبة السحابية

من التوقف التام إلى النجاة: كيف أنقذتنا استراتيجية “الضوء المرشد” (Pilot Light) يوم انقطعت السحابة؟

أتذكر ذلك اليوم جيدًا، فنجان القهوة الصباحي، وصوت تنبيهات المراقبة يصرخ كأنه يوم القيامة. كانت منطقة سحابية كاملة قد توقفت عن العمل، لكن بفضل استراتيجية...

4 يونيو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت مهمتي البرمجية للاختبار مجرد كود: كيف أنقذني توثيق القرارات من جحيم الصمت بعد المقابلة؟

أشارككم قصة حقيقية من بداياتي، وكيف تعلمت بالطريقة الصعبة أن المهمة البرمجية ليست مجرد كتابة كود، بل هي فرصة لإظهار طريقة تفكيرك. اكتشف كيف يمكن...

4 يونيو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

من الانتظار لأيام إلى الدفع في ثوانٍ: كيف أنقذتنا شبكات الدفع الفوري من جحيم التحويلات البنكية؟

أسرد لكم من واقع تجربتي كـ "أبو عمر"، كيف عانينا من بطء وتكلفة التحويلات البنكية الدولية، وكيف جاءت شبكات الدفع الفوري ومعيار ISO 20022 لتكون...

4 يونيو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كان كل خادم لدينا ‘ندفة ثلج’ فريدة: كيف أنقذنا ‘الكود كبنية تحتية’ (IaC) من جحيم الانجراف اليدوي؟

في هذه المقالة، أشارككم قصة حقيقية من قلب المعركة التقنية مع "خوادم ندفات الثلج" الفوضوية. سنغوص في مفهوم "الكود كبنية تحتية" (IaC) وكيف أن أدوات...

4 يونيو، 2026 قراءة المزيد
اختبارات الاداء والجودة

كانت تغطية الاختبارات 100% لكن الأخطاء تتسرب: كيف أنقذنا “الاختبار الطفري” من جحيم الثقة الزائفة؟

كنا نظن أن تغطية الاختبار بنسبة 100% هي درعنا الواقي، لكن الأخطاء كانت تتسلل إلى الإنتاج كاللصوص في ليل بهيم. اكتشف كيف أنقذنا "الاختبار الطفري"...

4 يونيو، 2026 قراءة المزيد
البودكاست