كانت الشبكة السيئة تضاعف طلبات الشراء: كيف أنقذنا مفهوم “العطالة” (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 لعملية حساسة، توقف لحظة واسأل نفسك: “ماذا لو أُرسل هذا الطلب مرتين؟”. إذا كان الجواب “كارثة”، فأنت الآن تعرف ما يجب عليك فعله. ما تهملوها يا جماعة! 👍

أبو عمر

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

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

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

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

آخر المدونات

تسويق رقمي

كانت ميزانيتنا التسويقية تحترق: كيف أنقذتنا ‘نماذج الإحالة’ من جحيم تخمين العائد على الاستثمار؟

أتذكر جيداً تلك الاجتماعات المحمومة، حيث كانت الأرقام تتراقص على الشاشة والميزانية التسويقية تتآكل دون أن نعرف أين يذهب كل دولار. في هذه المقالة، أشارككم...

5 مايو، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

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

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

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

كانت سيرتي الذاتية تصرخ في الفراغ: كيف أنقذت ‘العلامة التجارية الشخصية المتخصصة’ طلبي من الثقب الأسود لأنظمة ATS؟

كنت أرسل عشرات السير الذاتية دون أي رد، وكأنها تتبخر في الهواء. في هذه المقالة، أسرد لكم قصتي مع أنظمة تتبع المتقدمين (ATS) وكيف كان...

5 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

كانت طلباتنا تنهار تحت الضغط: كيف أنقذتنا ‘طوابير الرسائل’ (Message Queues) من جحيم المعالجة المتزامنة؟

أشارككم قصة حقيقية عن يوم كادت فيه أنظمتنا أن تنهار بسبب الضغط الهائل، وكيف كانت "طوابير الرسائل" (Message Queues) هي طوق النجاة الذي أنقذنا. هذه...

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

من الحظ إلى الخوارزميات: كيف أنقذ التسجيل الائتماني البديل الشركات الناشئة من تحيز البنوك؟

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

5 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

كان النمو الوظيفي يعتمد على الحظ: كيف أنقذتنا ‘مصفوفات الكفاءات’ من جحيم استنزاف المواهب؟

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

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