لنفترض أن شراء خدمة يجب أن ينشئ مهمة واحدة لفريق التنفيذ. وصل إشعار الدفع، وأُنشئت المهمة، ثم انقطع الاتصال قبل أن يتلقى المرسل الرد. بعد قليل يصل الإشعار مرة أخرى. هل ينشئ النظام مهمة ثانية؟
هذا مثال افتراضي لمراجعة تصميم التكاملات، وليس شرحًا لما نُفذ داخل OVZA. فائدته أنه يكشف مشكلة لا تظهر عند تجربة المسار الناجح مرة واحدة: قد يصل الطلب مجددًا لسبب مشروع، بينما يجب تنفيذ أثره التجاري مرة واحدة فقط.
ابدأ بقاعدة العمل التي يجب أن تبقى صحيحة
في مثالنا، القاعدة هي: «كل عملية شراء مؤكدة تنشئ مهمة تجهيز واحدة». يجب أن تظل صحيحة إذا تكرر الإشعار أو توقف التنفيذ ثم عاد.
يسمى السلوك الذي يسمح بتكرار الطلب دون تكرار أثره غير المقصود idempotency. وتوضح AWS فائدة المعرّف الصريح للطلب، بدل اعتبار تطابق البيانات دليلًا كافيًا على أن المقصود هو العملية نفسها. شرح AWS لإعادة المحاولة الآمنة.
قد يشتري العميل الخدمة نفسها مرتين عن قصد. لذلك أختار مرجعًا للعملية، ولا أعتمد على اسم الخدمة بالعربية أو الإنجليزية أو عنوان البريد الإلكتروني. المطلوب منع تجهيز الشراء الواحد مرتين، مع السماح بعمليات شراء مستقلة حتى لو تشابهت تفاصيلها.
اقرأ التزامات مزود الخدمة
قبل كتابة المعالج، أراجع ما تقوله وثائق المزود عن الإرسال. مثلًا، توضح Stripe احتمال تكرار الأحداث وإعادة إرسالها وعدم ضمان ترتيب وصولها، وتطلب التحقق من مصدر إشعارات الويب قبل تنفيذ أثرها. وثائق Stripe لإشعارات الويب.
لا أفترض أن بوابة أخرى تخدم السوق السعودي أو المصري تتبع القواعد نفسها. أبحث عن معرّفات الأحداث، وآلية التحقق، وسياسة إعادة المحاولة، وطريقة الاستعلام عن العملية. وما لا تجيب عنه الوثائق يبقى سؤالًا واضحًا يحتاج إلى حسم.
ثم أحدد معنى الرد الناجح في النظام الذي نبنيه. هل حفظنا الحدث بصورة دائمة لقبوله ومعالجته لاحقًا، أم أكملنا تنفيذه؟ إذا أرسلنا نجاحًا بينما النسخة الوحيدة من العمل المعلق موجودة في ذاكرة عملية قد تتوقف، فلن تكون لدينا نقطة موثوقة لاستئنافه.
افصل بين تسجيل العمل وإتمامه
قد ينجح تحديث قاعدة البيانات ويفشل إرسال الرسالة أو إنشاء المهمة الخارجية. أراجع هذه المسافة بين الفعلين بدل اعتبارهما عملية واحدة مضمونة.
أحد التصاميم الممكنة هو transactional outbox: حفظ التغيير وسجل الرسالة المنتظرة داخل معاملة واحدة، ثم إرسال الرسالة من عامل منفصل. يعالج ذلك مشكلة الكتابة في مكانين، مع بقاء الحاجة إلى التعامل مع الرسائل المتكررة لدى المستقبِل. دليل AWS لهذا النمط.
لكن هذا الاختيار يضيف تشغيلًا ومتابعة لحالات الإرسال وإعادة المحاولة. قد تكفي آلية أبسط لحفظ المهام، وقد تكون المنصة المستخدمة توفرها أصلًا. أختار ما يعالج الفشل المتوقع ويستطيع الفريق تشغيله وصيانته، وليس اختيارًا يُعجب المخططات المعمارية بأسمائها الأكثر تعقيدًا.
النتيجة غير المعروفة تحتاج إلى حالة واضحة
إذا انتهت مهلة الاتصال بعد إرسال طلب إنشاء المهمة، فقد تكون المهمة أُنشئت بالفعل. غياب الرد لا يساوي فشل التنفيذ. إعادة الطلب بمعرّف جديد قد تنشئ نسخة ثانية.
إذا كان المزود يدعم مفتاح idempotency، يُعاد استخدامه لمحاولات العملية نفسها وفق شروطه ومدد حفظه. توفر Stripe مثل هذه الآلية لطلبات واجهتها. توثيق طلبات Stripe القابلة لإعادة المحاولة.
في المثال المقترح، أُظهر حالة «النتيجة غير مؤكدة» حتى يمكن مطابقة الطلب مع النظام الخارجي. وإذا لم تتوفر وسيلة للاستعلام أو إعادة المحاولة بأمان، فقد يلزم فحص يدوي من شخص مخول. يحتاج الموظف إلى إجراء استعادة محدد، لا زر يعيد تشغيل المسار كله دون معرفة ما حدث.
اختبر نقاط الانقطاع نفسها
أختبر وصول نسخة ثانية بعد النجاح، ووصول نسختين في الوقت نفسه، وتأخر حدث قديم، وتوقف التنفيذ بعد إنشاء المهمة الخارجية وقبل تسجيل النتيجة محليًا.
في كل تجربة أفحص سجل العمل وعدد المهام، لا كود الاستجابة وحده. وأحتفظ بمعلومات تربط الحدث بعملية الشراء والمحاولة، دون نسخ بيانات شخصية لا يحتاجها السجل. كذلك أحدد من يحق له الاطلاع على الحالة وتشغيل الاستعادة.
التصميم المفيد يجعل فريق التشغيل قادرًا على التمييز بين ما ينتظر، وما اكتمل، وما يحتاج إلى فحص. تواصل معي إذا كان فريقك يحتاج إلى مطور يربط قرارات التكامل بسير العمل الفعلي.
أسئلة شائعة
لماذا قد يصل حدث التكامل أكثر من مرة؟
قد يعيد المصدر الإرسال عندما لا يستطيع تأكيد الاستلام. يحتاج المسار المستقبِل إلى التعرف على الأحداث المعالجة قبل تكرار إجراء تجاري.
هل يكفي منع تكرار رسائل البريد؟
لا. نراجع كل إجراء له أثر، بما فيه السجلات والمدفوعات وتغييرات الحالة. ونحدد ما يمكن إعادة محاولته وما يحتاج إلى فحص.