نمط Outbox التبادلي: النشر الموثوق للأحداث في الخدمات المصغرة
في الهندسة البرمجية الموزعة الحديثة، أصبحت البنية القائمة على الأحداث (Event-Driven Architecture) العمود الفقري لأنظمة الخدمات المصغرة القابلة للتوسع. تقوم الخدمات بانتظام بإصدار أحداث المجال مثل OrderCreated أو PaymentProcessed للتواصل عبر حدود الخدمات دون اقتران وثيق.
ومع ذلك، فإن تطبيق نشر الأحداث الموثوق به يطرح مشكلة هندسية دقيقة وحرجة: كيف تضمن أن تحديث قاعدة البيانات ونشر الحدث المقابل ينجحان معًا أو يفشلان معًا؟
إذا قامت الخدمة المصغرة بتحديث قاعدة البيانات المحلية (مثل PostgreSQL أو MySQL) ثم حاولت نشر حدث عبر الشبكة إلى وسيط الرسائل (مثل Apache Kafka أو RabbitMQ)، فإن انقطاع الشبكة أو تعطل الوسيط يمكن أن يؤدي إلى تلف سلامة البيانات.
لحل هذه المشكلة بشكل جذري، يعتمد مهندسو الأنظمة على نمط Outbox التبادلي (Transactional Outbox Pattern).
في هذا الدليل الشامل، سنقوم بتحليل مشكلة الكتابة المزدوجة (Dual-Write Problem)، والغوص في تفاصيل نمط Outbox، ومقارنة ناشري الاستطلاع مقابل التقاط تغيير البيانات (Debezium CDC)، وتوفير أمثلة برمجية بلغة Java و Go.
المشكلة الأساسية: ثغرة الكتابة المزدوجة
لنحلل ما يحدث عندما تحاول خدمة مصغرة تنفيذ “كتابة مزدوجة” تقليدية:
- كتابة بيانات العمل: إدراج سجل جديد في جدول
ordersالمحلي. - نشر حدث المجال: إرسال
OrderCreatedEventإلى Apache Kafka.
إذا نجح حفظ قاعدة البيانات ولكن فشل الاتصال بالشبكة أثناء الإرسال إلى Kafka، يتم حفظ الطلب ولكن لا يتم إخطار الخدمات الأخرى. النتيجة: عدم اتساق البيانات.
ما هو نمط Outbox التبادلي؟
يحصل هذا النمط على اسمه من فكرة الاستفادة من المعاملات المحلية ACID Transactions في قواعد البيانات.
بدلاً من محاولة إرسال رسالة مباشرة عبر الشبكة، تقوم الخدمة بكتابة حمولة الحدث في جدول مخصص يُسمى Outbox Table داخل نفس المعاملة المحلية التي تحفظ الكيان الأساسي.
بما أن الكيان وسجل Outbox ينتميان لنفس قاعدة البيانات، فإن خيار المعاملات يضمن إما كتابة السجلين معًا أو إلغائهما معًا.
بعد ذلك، تقوم عملية خلفية مستقلة (Message Relay) بقراءة سجلات Outbox ونشرها إلى وسيط الرسائل.
استراتيجيات ترحيل الرسائل: Polling Publisher مقابل CDC (Debezium)
1. Polling Publisher (المستطلع المجدول)
وظيفة خلفية تقوم بطلب SELECT * FROM outbox_events WHERE processed = FALSE بانتظام ونشرها إلى Kafka ثم تحديث الحالة.
- المزايا: سهل التنفيذ ولا يتطلب بنية تحتية إضافية.
- العيوب: استهلاك موارد قاعدة البيانات وزيادة زمن التأخير.
2. Change Data Capture (CDC مع Debezium)
تتبع سجلات معاملات قاعدة البيانات (PostgreSQL WAL) مباشرة ونشر التغييرات فور حدوثها.
- المزايا: زمن تأخير شبه معدوم وأداء عالي جداً.
- العيوب: يتطلب إعداد Kafka Connect و Debezium.
الخلاصة
يعد نمط Outbox التبادلي حلاً أساسياً لبناء خدمات مصغرة موزعّة وموثوقة تضمن اتساق البيانات حتى في حالة حدوث أعطال في الشبكة أو وسائط الرسائل.