لماذا تنجح عملية التكامل في بيئة العرض وتفشل بصمت في نظام الإنتاج؟

في اختبار القبول القياسي، تُرسل رسالة واحدة، ويُلاحظ وصولها، ثم تُعتمد. في نظام الإنتاج، تُعالج عملية التكامل نفسها آلاف الرسائل يوميًا، وبعضها سيفشل – بسبب انتهاء المهلة (Timeout)، أو قفل السجل (Row Lock)، أو انتهاء صلاحية التفويض (Authorization)، أو تغيير المخطط (Schema) في النظام الآخر. السؤال الذي يحدد جودة الحل ليس "هل يعمل التكامل"، بل "ماذا يحدث عندما لا يعمل، ومن يلاحظ ذلك".

معظم الإخفاقات المُكلفة التي رأيتها لم تنجم عن خطأ برمجي (Bug) في كود التكامل نفسه، بل عن غياب ثلاث قدرات: التعرف على فشل الرسالة، وآلية لإعادة المحاولة دون تكرار، وعملية للتحقق من تطابق البيانات في كلا النظامين بنهاية اليوم. بدون هذه القدرات، أي عملية تكامل "تعمل" حتى اللحظة التي يكتشف فيها أنها لم تعمل لمدة أسبوعين.

الطبقات الأربع التي تشكل المعالجة الصحيحة للأخطاء

الطبقةماذا تحلالفشل النموذجي بدونها
Idempotencyإعادة تشغيل نفس الرسالة لا يؤدي إلى إنشاء سجل مكررطلب مزدوج أو حركة مخزون مزدوجة بعد إعادة المحاولة
Retry مع Backoffالفشل المؤقت (Timeout, Rate Limit) يُصحح تلقائيًاالحمل اللحظي يتحول إلى عطل دائم
Dead Letter Queueالفشل غير المؤقت يُعلَم به ولا يختفي بصمترسالة "تُبتلع" ويعتقد الطرفان أنها عُولجت
Reconciliation الأعمالتُكتشف الفروقات في البيانات التي لم تستطع أن تفشل بوضوحتقرير شهري يكشف فرقًا يصعب تتبع مصدره

كل طبقة تعتمد على سابقتها. إعادة المحاولة (Retry) بدون تميز العملية (Idempotency) يؤدي إلى تكرار البيانات؛ قائمة الرسائل المتوفاة (Dead Letter) بدون تسوية الأعمال (Reconciliation) تخفي حقيقة أن الرسائل التي نجحت "تقنيًا" لم تعكس بالضرورة الوضع التجاري الصحيح.

Idempotency: المفتاح الذي يمنع التكرار

أي عملية تكامل يمكنها استقبال نفس الرسالة أكثر من مرة – وكل عملية تكامل تقريبًا كذلك – تحتاج إلى مفتاح فريد خارجي (External ID) يحدد الحدث، وليس فقط السجل. في Salesforce، التنفيذ الشائع هو Upsert بناءً على حقل External ID مع قيد فريد (Unique Constraint)، بالإضافة إلى جدول سجل (Custom Object أو Platform Event Log) يسجل أي معرفات أحداث تم التعامل معها بالكامل.

الخطأ الشائع: الاكتفاء بـ Upsert على السجل التجاري نفسه (مثل Order External ID) دون توثيق الخطوات الوسيطة. إذا كانت العملية تتضمن أيضًا تحديث المخزون في نظام خارجي، فإن Upsert على الطلب لا يمنع استدعاءً مزدوجًا لتحديث المخزون – يجب أن يكون كل إجراء فرعي له تأثير جانبي خارجي (Side Effect) متميزًا بذاته (Idempotent)، وليس فقط السجل النهائي.

Retry: سياسة التراجع وتصنيف الأخطاء

ليست كل الأخطاء تستحق إعادة المحاولة (Retry). يجب الفصل مسبقًا بين ثلاث فئات:

  • الأخطاء المؤقتة (Timeout, 503, Rate Limit) - مرشحة لإعادة المحاولة مع التراجع الأسي (Exponential Backoff)، أي أن الفاصل الزمني بين المحاولات يزيد (على سبيل المثال 30 ثانية، دقيقتين، 10 دقائق) لتجنب تفاقم الحمل.
  • الأخطاء الهيكلية (حقل إلزامي مفقود، انتهاك قاعدة التحقق، قيمة غير صالحة) - لن تُعاد محاولتها، لأنها ستفشل مرة أخرى بنفس الطريقة. يجب أن تنتقل مباشرة إلى قائمة الرسائل المتوفاة (Dead Letter).
  • أخطاء التفويض أو التكوين (رمز مميز منتهي الصلاحية، تغيير إصدار الـ API) - تتطلب تنبيهًا فوريًا للفريق التقني، لأنها تحجب جميع الرسائل في قائمة الانتظار وليس رسالة واحدة فقط.

في Salesforce، يُنفذ Retry عادةً في طبقة Middleware أو في Apex Queueable/Batch مع عداد محاولات محفوظ على السجل نفسه. عدد المحاولات المعقول لمعظم الحالات هو 3-5 مع التراجع (Backoff)، وليسRetry غير محدود – إعادة المحاولة غير المحدودة تحول عطلًا مؤقتًا إلى حمل مستمر على كلا النظامين.

Dead Letter Queue: حيث "تعيش" الرسائل الفاشلة

قائمة الرسائل المتوفاة (Dead Letter) ليست مجرد مكان تخزين – إنها عقد. يجب أن تحتوي كل رسالة تصل إليها على: معرف حدث أصلي، حمولة كاملة (Payload)، سبب فشل مصنّف، عدد المحاولات التي تمت، ووقت الدخول إلى قائمة الانتظار. بدون هذه المعلومات، يصبح "التعامل" مع قائمة الرسائل المتوفاة مجرد تخمين.

نهجان شائعان للتنفيذ في Salesforce:

  1. كائن مخصص مخصص (Integration_Failed_Message__c) مع حقول منظمة وعرض قائمة (List View) حسب نوع الخطأ – مناسب عندما تكون الشفافية مطلوبة للفريق التجاري داخل Salesforce نفسه.
  2. قائمة انتظار خارجية في طبقة Middleware (مثل Dead Letter Exchange في MuleSoft/Boomi) – مناسب عندما يراقب الفريق التقني من خارج Salesforce ويريد تجنب الحمل على المنظمة (Org).

يعتمد الاختيار على من يجب أن يتخذ إجراءً بشأن الفشل: إذا كان مالك عملية تجارية، فعليه رؤية ذلك داخل Salesforce؛ إذا كان فريق تكامل تقني، فمن الأفضل في الطبقة الخارجية.

التسوية التجارية (Business Reconciliation): الفحص الذي يكشف ما لم تلتقطه إعادة المحاولة

حتى مع Idempotency وRetry المثاليين، هناك حالات فشل "تنجح" من الناحية التقنية ولكنها تخلق فجوة تجارية – على سبيل المثال، رسالة تم استلامها ومعالجتها، ولكن بقيمة خاطئة تم الحصول عليها من مصدر بيانات غير محدث. التسوية التجارية هي عملية دورية (يومية، كل ساعة، حسب معدل الأحداث) تقارن عددًا أو مجموعًا تراكميًا بين النظامين – على سبيل المثال، عدد الطلبات التي تم إنشاؤها في ERP مقابل عدد الطلبات التي تم إنشاؤها في Salesforce لنفس اليوم – وتسلط الضوء على الفجوات قبل أن تتحول إلى مشكلة خدمة للعملاء.

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

إطار العمل لاتخاذ القرار: متى تكون كل طبقة إلزامية ومتى يمكن الاستغناء عنها

المعيارIdempotency إلزاميRetry تلقائي إلزاميDead Letter منفصل إلزاميReconciliation يومي إلزامي
يؤدي الحدث إلى حركة مالية أو مخزوننعمنعمنعمنعم
الحدث أحادي الاتجاه، للقراءة فقط (Read)غير حاسمنعملالا
حجم رسائل يتجاوز 500 رسالة يوميًانعمنعمنعمموصى به
شريك خارجي بدون اتفاقية مستوى خدمة (SLA) عالية التوفرنعمنعم، مع Backoff طويلنعمموصى به
تكامل بين كائنين غير ماليين بحجم منخفضموصى بهموصى بهغير ضروريلا

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

سيناريو مثال: بائع تجزئة مع مزامنة طلبات ثنائية الاتجاه

تُدير شركة بيع بالتجزئة لديها 40 فرعًا Salesforce لإدارة طلبات B2B ونظام ERP خارجي للمخزون والفواتير. تم بناء التكامل في الأصل باستدعاء REST بسيط: عندما يُنشأ طلب في Salesforce، يقوم استدعاء متزامن بإنشائه في ERP. بدون Retry، بدون Dead Letter.

خلال فترة الذروة (الجمعة السوداء)، بدأ نظام ERP بإرجاع مهلة (Timeout) في حوالي 3% من الاستدعاءات. بدون آلية Retry، اختفت هذه الـ 3% ببساطة – ظل الطلب في Salesforce في حالة "تم الإرسال" دون أن يعرف نظام ERP عنه. في غضون يومين، تراكم حوالي 140 طلبًا لم تصل إلى عملية التعبئة، وتم اكتشافها فقط عندما اتصل العملاء للاستفسار عن بضاعتهم.

الحل الذي تم بناؤه بعد ذلك: طبقة Queueable في Apex تُعيد المحاولة حتى 5 مرات مع تأخير زمني (Backoff) قدره 1/5/15/30/60 دقيقة؛ حقل ERP_Sync_Status__c بقيم Pending/Synced/Failed؛ كائن مخصص (Integration_Failed_Message__c) يجمع الإخفاقات النهائية مع زر "أعد المعالجة" لفريق العمليات؛ وتقرير تسوية يومي يقارن عدد الطلبات بين الأنظمة ويرسل تنبيهًا (Slack Alert) عندما يتجاوز الفرق الصفر. انخفض وقت الكشف عن مشكلة مماثلة من يومين إلى أقل من ساعة.

المخاطر الشائعة والإجراءات الوقائية

الخطركيف يبدو في الواقعإجراء وقائي
Retry غير محدود في خطأ هيكلينفس الرسالة تفشل مرارًا وتكرارًا وتولد حملًاتصنيف الأخطاء مسبقًا وإرسال الأخطاء الهيكلية مباشرة إلى Dead Letter
غياب مفتاح فريد للحدثRetry أو استدعاء مزدوج يؤدي إلى إنشاء سجل مكررExternal ID على الحدث، وليس فقط على السجل النهائي
Dead Letter بدون مالك (Owner)تتراكم الرسائل ولا يقوم أحد بإغلاقهاتحديد مالك و SLA للمعالجة حسب نوع الحدث، وليس حسب النظام
مراقبة تقنية فقط (حالة API)التكامل "أخضر" لكن معلومات العمل غير متطابقةإضافة Reconciliation يقارن النتائج التجارية، وليس فقط كود الاستجابة
Backoff ثابت وقصير جدًاالمحاولات المتكررة تزيد الحمل أثناء عطل واسع النطاقExponential Backoff مع سقف محدد لعدد المحاولات

قائمة التحقق قبل اعتماد تصميم معالجة الأخطاء

  • ☐ لكل حدث مفتاح فريد (External ID) يمنع التكرار عند إعادة التشغيل.
  • ☐ تُصنف الأخطاء مسبقًا إلى مؤقتة / هيكلية / تفويض، مع معالجة مختلفة لكل نوع.
  • ☐ توجد سياسة Backoff محددة مع أقصى عدد للمحاولات.
  • ☐ توجد قائمة رسائل متوفاة (Dead Letter) سهلة الوصول تحتوي على حمولة كاملة (Payload) وسبب الفشل.
  • ☐ يوجد مالك (Owner) واتفاقية مستوى خدمة (SLA) محددة لكل نوع من أنواع الفشل.
  • ☐ توجد عملية تسوية (Reconciliation) دورية تقارن النتائج التجارية بين الأنظمة.
  • ☐ تصل التنبيهات إلى قناة يتم قراءتها بالفعل (وليس فقط إلى السجلات).
  • ☐ يتضمن سيناريو الاختبار توقف خدمة النظام الآخر، وليس فقط المسار السعيد (Happy Path).

كيف يترابط هذا مع باقي البنية الأساسية

لا يقف تصميم معالجة الأخطاء بمفرده – فهو يعتمد على طبقة البيانات والأذونات المحددة في دليل بنية CRM، وعلى قرار ما إذا كان المنطق ينفذ في Flow أو Apex وفقًا لـ Salesforce Flow أو Apex. يجب مراجعة نموذج الأذونات الذي تكتب مكونات التكامل من خلاله إلى البيانات مقابل نموذج أذونات Salesforce، لضمان عدم حصول المستخدم التقني للتكامل على وصول واسع النطاق للغاية. وعندما يزداد حجم الرسائل، تتلاقى مسألة إعادة المحاولة (Retry) مباشرة مع قيود API المفصلة في حدود Salesforce API.

الملخص

معالجة أخطاء التكامل ليست ميزة تُضاف في النهاية – إنها الفرق بين نظام يُكتشف أنه معطل بعد شكوى من العميل، ونظام ينبه بنفسه قبل تراكم الضرر. الطبقات الأربع – Idempotency، Retry المصنفة، Dead Letter مع مالك، والتسوية التجارية – لا تتطلب مشروعًا منفصلاً، لكنها تتطلب قرارًا صريحًا في مرحلة التصميم، قبل أن يتم نشر أول عملية تكامل في نظام الإنتاج. المنظمة التي تحذر نفسها عند فشل 3% من الرسائل في غضون ساعة تختلف جوهريًا عن المنظمة التي تكتشف ذلك من عميل غاضب.