مفتاح عدم تكرار المعاملة (Idempotency Key): طوق النجاة الذي أنقذنا من فوضى الطلبات المكررة

السلام عليكم يا جماعة الخير، معكم أخوكم أبو عمر.

خلوني أرجع فيكم بالزمن لكم سنة لورا. كنا في عز الشغل، سهرانين على إطلاق ميزة دفع جديدة لواحد من أكبر عملائنا. القهوة ما كانت تفارق إيدينا، والعيون حمرا من كثر التحديق في الشاشات. أطلقنا الميزة الساعة 12 بالليل، والحمد لله، الأمور بدت تمام التمام. احتفلنا احتفال بسيط، وكل واحد فينا حط راسه على مخدته وهو بيحلم بالنجاح.

ما كملت نص ساعة إلا والتلفون برن… صوت مدير الدعم الفني على الطرف الثاني مذعور: “يا أبو عمر الحقنا! المصاري بتنخصم من العملاء مرتين! الفيسبوك ولّع شكاوي والناس معصبة!”.

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

وقتها، تذكرت مفهوم كنت قرأت عنه زمان اسمه “Idempotency”. في هذيك الليلة، ما كان هالمفهوم مجرد مصطلح تقني، بل كان طوق النجاة اللي أنقذنا من جحيم الفوضى. اليوم، بدي أشارككم القصة كاملة، والدرس اللي تعلمناه بالطريقة الصعبة.

ما هو “عدم تكرار المعاملة” (Idempotency)؟ شرح مبسط للجدعان

ببساطة شديدة، “Idempotency” هي خاصية في عملية ما، بتخليك لو نفذتها مرة أو ألف مرة، النتيجة النهائية تكون واحدة وثابتة. ما فهمت؟ بسيطة.

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

في عالم الـ APIs، بعض الطلبات (HTTP Methods) هي بطبيعتها “Idempotent”:

  • GET, HEAD, OPTIONS, TRACE: هاي الطلبات آمنة أصلاً، لأنها ما بتغير أي شيء في الخادم. لو طلبت بيانات منتج معين مليون مرة، المنتج ما رح يتغير.
  • PUT: هذا الطلب يعتبر Idempotent. لو أرسلت طلب PUT /articles/123 لتحديث مقال بمحتوى معين، سواء أرسلته مرة أو خمسين مرة، النتيجة النهائية هي أن المقال 123 سيحتوي على ذلك المحتوى.
  • DELETE: نعم، حتى الحذف! لو أرسلت DELETE /users/456، أول مرة سيتم حذف المستخدم. ثاني مرة (وثالث ورابع مرة)، الخادم رح يرجعلك “404 Not Found”، لكن النتيجة النهائية على النظام ما تغيرت: المستخدم 456 محذوف.

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

‘مفتاح عدم تكرار المعاملة’ (Idempotency Key): المنقذ من الفوضى

الحل يكمن في إجبار طلب الـ POST على أن يتصرف كأنه Idempotent. كيف؟ من خلال شيء بنسميه “مفتاح عدم تكرار المعاملة” أو بالإنجليزية “Idempotency Key”.

الفكرة عبقرية في بساطتها: العميل (المتصفح أو تطبيق الموبايل) هو من يقوم بإنشاء مُعرّف فريد (unique ID) لكل معاملة يحاول تنفيذها. ثم يقوم بإرسال هذا المُعرّف مع الطلب داخل الـ Header.

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

كيف يعمل السحر؟ آلية العمل خطوة بخطوة

  1. العميل يُنشئ المفتاح: قبل إرسال طلب POST لإنشاء طلبية جديدة، يقوم كود الـ JavaScript في المتصفح بإنشاء مُعرّف فريد عالمياً (UUID). مثلاً: 'f1b2c3d4-e5f6-7890-1234-abcdabcdabcd'.
  2. العميل يُرسل الطلب: يتم إرفاق هذا المفتاح في هيدر خاص، وليكن اسمه Idempotency-Key.
  3. الخادم يستقبل الطلب لأول مرة:
    • الخادم يقرأ الهيدر Idempotency-Key.
    • يبحث عن قيمة هذا المفتاح في مكان تخزين مؤقت (مثل Redis أو جدول في قاعدة البيانات).
    • لا يجد المفتاح. هذا يعني أنها معاملة جديدة.
    • قبل أن يبدأ بالعملية الفعلية (الخصم من البطاقة مثلاً)، يقوم بتخزين المفتاح وحالة “قيد التنفيذ” (In Progress). هذا يمنع حدوث تضارب لو وصل طلب آخر بنفس المفتاح في نفس اللحظة.
    • ينفذ العملية بنجاح (تم إنشاء الطلبية وخصم المبلغ).
    • يُحدّث حالة المفتاح إلى “مكتمل” (Completed) ويخزن بجانبه الاستجابة الكاملة التي سيرسلها للعميل (Response Body and Status Code).
    • يرسل الاستجابة الناجحة (e.g. 201 Created) للعميل.
  4. الخادم يستقبل الطلب المكرر (النقرة المزدوجة):
    • بعد ثوانٍ معدودة، يصل طلب آخر بنفس البيانات وبنفس Idempotency-Key.
    • الخادم يبحث عن المفتاح في مخزنه المؤقت.
    • يجد المفتاح! وحالته “مكتمل”.
    • هنا، لا يقوم الخادم بتنفيذ منطق إنشاء الطلبية مرة أخرى على الإطلاق. لا خصم جديد، لا طلبية جديدة.
    • بكل بساطة، يسترجع الاستجابة التي خزنها سابقاً ويرسلها مرة أخرى للعميل كما هي.

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

كود يا مبرمج: مثال عملي باستخدام Node.js و Express

الكلام النظري جميل، لكن خلينا نشوف كود. هذا مثال مبسط جداً لفكرة “middleware” في Express.js يمكنه التعامل مع طلبات الـ Idempotency.

ملاحظة: في هذا المثال سأستخدم كائن بسيط في الذاكرة لتخزين المفاتيح. في بيئة الإنتاج الحقيقية، يجب استخدام حل دائم وسريع مثل Redis أو قاعدة بيانات.


// in-memory store (for demonstration only!)
// In production, use Redis or a database.
const idempotencyStore = new Map();

const idempotencyMiddleware = async (req, res, next) => {
  const idempotencyKey = req.get('Idempotency-Key');

  // If no key, just proceed. Maybe log a warning.
  if (!idempotencyKey) {
    return next();
  }

  // Check if we have this key in our store
  if (idempotencyStore.has(idempotencyKey)) {
    const cachedResponse = idempotencyStore.get(idempotencyKey);
    
    // If it's still processing, you might return a 409 Conflict
    if (cachedResponse.status === 'in-progress') {
        return res.status(409).json({ message: 'Request is already being processed.' });
    }

    // If it's completed, return the cached response
    console.log(`[Idempotency] Returning cached response for key: ${idempotencyKey}`);
    return res.status(cachedResponse.statusCode).json(cachedResponse.body);
  }

  // --- New Request ---
  // 1. Store the key as "in-progress"
  idempotencyStore.set(idempotencyKey, { status: 'in-progress' });
  console.log(`[Idempotency] New key received, processing: ${idempotencyKey}`);

  // 2. We need to "spy" on the original res.json() to cache the response
  const originalJson = res.json;
  res.json = (body) => {
    // Cache the successful response
    if (res.statusCode >= 200 && res.statusCode < 300) {
      const responseToCache = {
        status: 'completed',
        statusCode: res.statusCode,
        body: body,
      };
      // Set an expiry for the key, e.g., 24 hours
      idempotencyStore.set(idempotencyKey, responseToCache);
    } else {
      // If the request failed, we can remove the key to allow retries
      idempotencyStore.delete(idempotencyKey);
    }
    // Call the original res.json()
    return originalJson.call(res, body);
  };

  // 3. Proceed to the actual controller
  next();
};


// How to use it in your Express app
// app.post('/api/orders', idempotencyMiddleware, createOrderController);

نصائح من قلب الميدان: خبرة أبو عمر

  • أين يُولّد المفتاح؟ دائماً وأبداً في طرف العميل (Client-side). لا تجعل الخادم “يخمن” أن الطلب مكرر بناءً على محتوياته. اجعل الأمر صريحاً ومبنياً على مفتاح يرسله العميل.
  • كم مدة تخزين المفتاح؟ لا تخزنه للأبد! هذا سيؤدي إلى تضخم قاعدة بياناتك أو ذاكرة Redis بلا داعي. فترة 24 ساعة تعتبر معياراً جيداً ومقبولاً في معظم الحالات. بعد 24 ساعة، من الآمن افتراض أن المستخدم لن يعيد إرسال نفس المعاملة.
  • ماذا عن أخطاء الشبكة؟ هذه هي إحدى أروع فوائد هذا النمط. تخيل أن الخادم نفذ العملية بنجاح، لكن الاتصال انقطع قبل أن تصل الاستجابة للعميل. العميل سيعتقد أن العملية فشلت وسيحاول مرة أخرى. بوجود مفتاح الـ Idempotency، المحاولة الثانية لن تنفذ العملية مرة أخرى، بل ستعيد إرسال الاستجابة الناجحة المخزنة، وهذا بالضبط ما نريده.
  • “خاوا” (إجبارياً): في فريقي، أصبح استخدام مفتاح Idempotency “خاوا” (إجبارياً) لأي نقطة نهاية (endpoint) تقوم بعملية إنشاء أو تعديل حساسة وغير قابلة للعكس بسهولة. لا تعتبره رفاهية، بل هو أساس لنظام قوي وموثوق.

الخلاصة والزبدة 🥑

في عالم اليوم، حيث نتوقع من الأنظمة أن تكون متاحة وموثوقة 100% من الوقت، لم تعد المشاكل مثل الطلبات المكررة مقبولة. المستخدم لا يرحم، وسمعة تطبيقك على المحك.

مفهوم “مفتاح عدم تكرار المعاملة” هو نمط تصميم بسيط لكنه فعال بشكل لا يصدق. يحول نقاط الضعف (الشبكات غير المستقرة، المستخدمين غير الصبورين) إلى جزء يمكن التعامل معه بأمان داخل نظامك.

نصيحتي الأخيرة لك: ما تستنى المشكلة تصير معك وتتعلم بالطريقة الصعبة زي ما صار معنا. كن استباقياً، وافهم هذا المفهوم جيداً، وادمجه في تصميم الواجهات البرمجية (APIs) الحساسة عندك من اليوم. صدقني، راحة بالك وتقدير عملائك يستحقان هذا الجهد البسيط. 👍

أبو عمر

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

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

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

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

آخر المدونات

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

تحديثات قاعدة البيانات بدون توقف: كيف أنقذنا نمط التوسيع والتعاقد (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 قراءة المزيد
البودكاست