وصول webhook يعني أن نظامًا خارجيًا يحاول إخبار تطبيقك بحدث، وليس وعدًا بأن الرسالة ستصل مرة واحدة وبالترتيب المتوقع. إذا فعّلت عضوية عند كل إشعار دفع دون ضبط التكرار، قد تمدّد الصلاحية مرتين. التصميم الصحيح يبدأ بافتراض إعادة الإرسال والانقطاع والتأخير، مع فصل الاستلام عن تنفيذ الأثر التجاري.
تحقّق قبل الحفظ والمعالجة
استخدم آلية توقيع مزوّد الخدمة وبنيتها الموثّقة. بعض المزوّدين، مثل Stripe، يحتاجون جسم الطلب الخام للتحقّق؛ إعادة تحويل JSON قد تغيّر البايتات وتفشل المطابقة. خزّن الأسرار خارج الكود، وحدّد حجم الطلب، وارفض التوقيع غير الصحيح. لا تستبدل التوقيع بفحص اسم حدث أو عنوان IP غير موثّق.
أنشئ صندوق أحداث دائمًا
بعد التحقّق، احفظ الحدث في قاعدة البيانات مع المزوّد والحساب الخارجي ومعرّف الحدث وحالة المعالجة. ضع قيدًا فريدًا على مفتاح مناسب لنطاق المزوّد، مثل provider + account_id + event_id. إرجاع نجاح دون حفظ موثوق قد يفقد الحدث إذا انهارت العملية قبل إرساله إلى الطابور. يمكن لعامل مستقل سحب الأحداث الجديدة من الصندوق، أو استخدام outbox لإيصال العمل إلى الطابور.
Verify signature -> persist event with unique key -> acknowledge
Worker -> claim event -> apply business transition -> mark processed
Failure -> record reason -> bounded retry or manual reviewامنع تكرار الأثر لا الرسالة فقط
قد تمثل رسالتان مختلفتان نفس الحقيقة التجارية. لذلك لا يكفي deduplication على event_id وحده. في مثال العضوية، اربط تفعيل المدة بمعرّف الدفعة أو دورة الفوترة، وضع قيدًا يمنع تسجيلها مرتين. نفّذ تعديل العضوية وتسجيل الأثر ضمن معاملة عندما يكونان في نفس القاعدة. إذا كان الأثر في خدمة خارجية، استخدم مفتاح idempotency يدعمه المزوّد أو آلية مصالحة؛ معاملة SQL لا تشمل الطلب الخارجي تلقائيًا.
تعامل مع التأخير كحالة طبيعية
قد يصل إشعار إلغاء قبل إشعار سابق لتحديث الاشتراك. صمّم انتقالات حالات تمنع الرجوع غير المنطقي، ولا تعتبر وقت وصول الرسالة ترتيبًا تجاريًا. عند الشك، اقرأ الحالة الحالية من المزوّد إذا كانت واجهته تدعم ذلك. احتفظ بنسخة الحدث وسياقه وفق سياسة احتفاظ مناسبة، مع تجنّب وضع معلومات حساسة في السجلات العامة.
جرّب سيناريوهات الانقطاع
أعد إرسال الحدث نفسه، ثم أرسل حدثين بالتوازي. أوقف العامل بعد تنفيذ الأثر وقبل تعليم الحدث كمكتمل. اختبر انقطاع قاعدة البيانات وقت الاستلام، وفشل خدمة البريد بعد نجاح الدفع، وتأخر حدث قديم. ينبغي أن تكون إعادة التشغيل آمنة، وأن يستطيع المشغّل معرفة ما تم وما يحتاج تدخّلًا.
راقب عمر الأحداث المتراكمة
عدد الأحداث وحده لا يكفي؛ راقب عمر أقدم حدث غير معالج ونسبة الفشل ومحاولات الإعادة. أضف مهمة مصالحة دورية للكيانات المهمة مثل المدفوعات. سياسات إعادة الإرسال تختلف بين المزوّدين، لذلك وثّق عقد التكامل الخاص بكل واحد بدل تعميم سلوك Stripe على جميع الخدمات.
سيناريو عملي: دفعة نجحت والعامل توقّف
تخيّل أن العامل فعّل العضوية ثم توقّف قبل تعليم الحدث كمكتمل. عند إعادة التشغيل، سيراها النظام رسالة غير منتهية. يجب أن يجد سجل الأثر التجاري أن الدفعة استُخدمت بالفعل، فينهي الحدث دون إضافة مدة ثانية. إذا كان تسجيل الأثر وتغيير العضوية في نفس قاعدة البيانات، اجمعهما في معاملة واحدة. أمّا البريد فيمكن أن يملك سجل إرسال مستقلًا حتى لا تتكرّر الرسالة عند إعادة محاولة بقية العملية.
ماذا تعرض للمشغّل؟
وفّر معرّف الحدث ومعرّف العملية والحالة وعدد المحاولات وآخر سبب فشل، مع رابط للسجل التجاري المرتبط. أداة إعادة التشغيل يجب أن تمر من نفس ضوابط التكرار، لا من مسار يتجاوزها. ميّز بين فشل مؤقت ونتيجة خارجية غير معروفة، فالثاني قد يحتاج استعلامًا أو مراجعة قبل المحاولة. لا تسمح بأن تصبح معالجة الأحداث الفاشلة نسخًا يدويًا للأوامر من سجلات الإنتاج.
المراجع الرسمية
تدعم هذه المراجع سلوك الأدوات المذكورة. الأمثلة وقرارات التصميم توضيحية، ويجب تكييفها مع متطلبات المشروع وإصداراته.
إعداد: Noor Yasser
تعمل على تحدٍ تقني مشابه؟
أساعد الفرق على تحويل القرار المعماري إلى نطاق واضح وتنفيذ يمكن تشغيله ومراجعته بثقة.




