تخطَّ إلى المحتوى

Transactional Outbox

نمط Outbox — التسليم الموثوق للأحداث Transactional Outbox

يضمن نشر حدث بعد تحديث قاعدة البيانات دون فقدانه، عبر تخزين الحدث في جدول ضمن نفس المعاملة المحلية بدل نشره مباشرة على Broker خارجي.

تحديث قاعدة البيانات ونشر حدث على Message Broker عمليتان منفصلتان على نظامين مختلفين — لو نجحت واحدة وفشلت الأخرى (بسبب عطل بين السطرين)، يفقد النظام اتساقه. Outbox يحل هذا بكتابة الحدث في جدول outbox عادي بنفس قاعدة البيانات وضمن نفس معاملة تحديث البيانات، فينجحان أو يفشلان معاً كوحدة واحدة.

عملية منفصلة (Message Relay) تقرأ لاحقاً الأحداث غير المنشورة من الجدول وتنشرها فعلياً — إما بالاستعلام الدوري (Polling Publisher) أو بقراءة سجل معاملات قاعدة البيانات مباشرة (Transaction Log Tailing، عبر أدوات مثل Debezium).

الصياغة

BEGIN TRANSACTION
  UPDATE business_table ...
  INSERT INTO outbox (event) VALUES (...)
COMMIT
-- عملية منفصلة تنشر لاحقاً من outbox

📄 مثال

await db.transaction(async (tx) => {
  const order = await tx.orders.save(data);
  await tx.outbox.insert({
    eventType: 'order.created', payload: order, publishedAt: null,
  });
}); // الاثنان ينجحان أو يفشلان معاً

// عملية خلفية منفصلة تنشر لاحقاً
for (const e of await db.outbox.findUnpublished()) {
  await broker.publish(e.eventType, e.payload);
  await db.outbox.markPublished(e.id);
}

أهم النقاط

العنصرالوظيفة
معاملة محلية واحدةتحديث البيانات وتسجيل الحدث يحدثان معاً أو لا يحدثان إطلاقاً
Message Relayعملية منفصلة تنشر الأحداث المسجَّلة فعلياً على الـ Broker

💡 نصائح عملية

  • المستهلك يجب أن يكون Idempotent — الضمان هو at-least-once لا exactly-once
  • لو كانت البنية التحتية جاهزة، Transaction Log Tailing (مثل Debezium) أسرع من الاستعلام الدوري

⚠️ أخطاء شائعة

  • نسيان إدراج الحدث في outbox عند إضافة مسار كود جديد يعدّل نفس الكيان
  • بناء مستهلك يفترض وصول كل حدث مرة واحدة بالضبط بلا تكرار محتمل

خصائص ذات صلة

🎓 تريد فهم الصورة الكاملة خطوة بخطوة؟ ابدأ من مسار MICROSERVICES الكامل بالعربي.