كانت خدماتنا تتحدث بصراخ: كيف أنقذتنا المعمارية الموجهة بالأحداث (EDA) من جحيم الاقتران الخانق؟

يا جماعة الخير، السلام عليكم ورحمة الله.

اسمحولي أبدأ بقصة صغيرة صارت معي قبل كم سنة. كنا شغالين على نظام كبير لشركة تجارة إلكترونية، نظام فيه كل إشي: إدارة للمنتجات، طلبات، دفع، شحن، خدمة عملاء… كل خدمة (service) كانت عبارة عن تطبيق صغير لحالها، والمفروض إنها “مايكروسيرفيس” يعني. بس يا ويلي شو كانت “مايكروسيرفيس”!

بتذكر مرة، فريق التسويق طلب تعديل بسيط: “بدنا لما الزبون يسجل حساب جديد، نبعتله إيميل ترحيب مع كود خصم”. قلنا بسيطة، شغلة نص ساعة. دخل المبرمج المسؤول عن خدمة التسجيل (User Service) وأضاف سطرين كود عشان ينادي خدمة الإشعارات (Notification Service). رفعنا التحديث، وكل شي تمام… لمدة يومين.

بعد يومين، صار في ضغط على سيرفر الإيميلات، وخدمة الإشعارات صارت بطيئة شوي. وهون بلّشت المصايب. صارت عملية تسجيل المستخدم الجديد تعلق! المستخدم يحط معلوماته، يكبس “تسجيل”، والصفحة تضلها تحمل… تحمل… لحد ما تطلعله رسالة خطأ. الدنيا قامت وقعدت، والزبائن بشتكوا، والإدارة فوق راسنا. ليش؟ لأنه خدمة التسجيل كانت مرتبطة “بالحبل السري” مع خدمة الإشعارات. إذا خدمة الإشعارات عطست، خدمة التسجيل بتصيبها إنفلونزا.

وقتها قعدت مع حالي، صفنت وحكيت: “يا أبو عمر، هيك ما بنفع نكمل. خدماتنا مش بتتكلم مع بعض، خدماتنا بتصرّخ على بعض! أي وحدة بتوقع بتسحب الباقي معها”. هذا الأخطبوط اللي اسمه “الاقتران الخانق” كان رح يغرق سفينتنا. ومن هون بلشت رحلتنا للبحث عن حل، والحل كان اسمه: المعمارية الموجهة بالأحداث (Event-Driven Architecture).

ما هو جحيم “الاقتران الخانق” (Tight Coupling)؟

قبل ما نحكي عن الحل، خلينا نفهم المشكلة منيح. تخيل إنك بتبني حيط من الحجر، بس بدل ما تستخدم مونة (إسمنت) بين الحجارة، قررت تلحمهم ببعض بالحديد. الحيط رح يكون قوي مبدئياً، بس لو بدك تغير حجر واحد بس؟ رح تضطر تكسر اللحام، وممكن تكسر الحجارة اللي جنبه. هذا هو بالضبط الاقتران الخانق في البرمجة.

لما تكون خدمة (أ) لازم تنادي خدمة (ب) مباشرة عشان تكمل شغلها، بنسمي هاي العلاقة “اقتران متزامن” (Synchronous Coupling). وهذا بيخلق عدة مشاكل كارثية:

  • نقطة فشل مركزية (Single Point of Failure): إذا وقعت خدمة (ب)، خدمة (أ) رح تفشل معها، وممكن خدمة (ج) اللي بتعتمد على (أ) تفشل كمان، وهكذا دواليك. بتصير زي أحجار الدومينو.
  • صعوبة التطوير: أي تغيير في خدمة (ب) (مثلاً تغيير في الـ API)، بيتطلب تغيير وتحديث في كل الخدمات اللي بتناديها. بتصير عملية التطوير بطيئة ومعقدة.
  • انعدام المرونة: صعب جداً تضيف خدمة جديدة (مثلاً خدمة “د”) لازم تستجيب لنفس الحدث بدون ما تعدل على الخدمة الأصلية (أ).

باختصار، الاقتران الخانق بحوّل نظامك من مجموعة خدمات مستقلة إلى كتلة واحدة متصلبة، أي تغيير فيها بصير زي عملية جراحية خطيرة.

طوق النجاة: المعمارية الموجهة بالأحداث (EDA)

طيب يا أبو عمر، شو الحل؟ الحل هو نغير طريقة تفكيرنا تماماً. بدل ما الخدمات تنادي بعضها مباشرة، رح نخليها تتواصل بطريقة غير مباشرة، مثل الصدى في وادٍ واسع. هذا هو جوهر الـ EDA.

في الـ EDA، الخدمة ما بتطلب شي من خدمة ثانية. هي فقط بتعلن عن “حدث” (Event) صار عندها. مثلاً، خدمة المستخدمين ما بتنادي خدمة الإشعارات، هي بس بتعلن للعالم كله: “يا عالم، لقد تم تسجيل مستخدم جديد! وهذا هو رقمه: 123”.

هذا الإعلان (الحدث) ما بروح بالهوا، بروح لمكان مركزي بنسميه “وسيط الأحداث” أو “ناقل الأحداث” (Event Broker / Message Bus). والخدمات الثانية اللي بهمها هالموضوع (مثل خدمة الإشعارات، خدمة التحليلات، خدمة CRM) بتكون “مشتركة” (Subscribed) في هذا النوع من الأحداث. لما وسيط الأحداث يستقبل الحدث، بوزعه على كل المشتركين المهتمين.

لاحظ الفرق الجوهري: خدمة المستخدمين (المنتج للحدث) ما بتعرف أصلاً مين رح يستقبل الحدث، ولا بهمها. هي عملت اللي عليها ورمت الخبر في الباص. وخدمة الإشعارات (المستهلك للحدث) ما بتعرف مين أنتج الحدث، هي بس بتعرف إنه وصلها حدث من النوع اللي بهمها ولازم تتعامل معه.

المكونات الأساسية للـ EDA

  1. الحدث (Event): رسالة صغيرة بتوصف شي صار في الماضي. مثلاً: OrderPlaced, UserRegistered, PaymentFailed. الحدث لازم يحتوي على كل المعلومات اللازمة للمستهلكين عشان يقوموا بعملهم.
  2. منتج الحدث (Event Producer): هو أي خدمة بتصدر أو بتنشر حدث معين. في مثالنا، خدمة الطلبات (Order Service) هي منتج لحدث OrderPlaced.
  3. مستهلك الحدث (Event Consumer): هو أي خدمة بتستمع أو بتشترك في نوع معين من الأحداث وبتتفاعل معه. في مثالنا، خدمة الإشعارات وخدمة المخزون هم مستهلكين لحدث OrderPlaced.
  4. وسيط الأحداث (Event Broker): هو القلب النابض للنظام. هو المسؤول عن استلام الأحداث من المنتجين وتوصيلها بشكل موثوق لكل المستهلكين المهتمين. أشهر الأمثلة عليه: RabbitMQ, Apache Kafka, Google Pub/Sub, AWS SNS/SQS.

مثال عملي: من الصراخ إلى الهمس

خلينا نرجع لمثال التجارة الإلكترونية ونشوف كيف الـ EDA بتحل المشكلة.

السيناريو القديم (الاقتران الخانق):

  1. الزبون يضغط “تأكيد الطلب” في الواجهة الأمامية.
  2. الواجهة تنادي الـ API تبع خدمة الطلبات (Order Service).
  3. خدمة الطلبات تحفظ الطلب في قاعدة البيانات.
  4. خدمة الطلبات تنادي مباشرة خدمة المخزون (Inventory Service) لخصم الكمية. (انتظار…)
  5. خدمة المخزون ترد بنجاح.
  6. خدمة الطلبات تنادي مباشرة خدمة الإشعارات (Notification Service) لإرسال إيميل. (انتظار…)
  7. خدمة الإشعارات ترد بنجاح.
  8. خدمة الطلبات ترد للواجهة الأمامية بأن الطلب تم بنجاح.

المشكلة: لو خدمة الإشعارات بطيئة أو واقعة، كل العملية بتعلق عند خطوة رقم 6، والزبون رح يشوف شاشة تحميل لا نهائية، وممكن الطلب يفشل كله.

السيناريو الجديد (معمارية الأحداث):

  1. الزبون يضغط “تأكيد الطلب”.
  2. الواجهة تنادي الـ API تبع خدمة الطلبات (Order Service).
  3. خدمة الطلبات تحفظ الطلب في قاعدة البيانات.
  4. خدمة الطلبات تنشر حدث اسمه OrderPlacedEvent إلى وسيط الأحداث (مثلاً RabbitMQ). الحدث يحتوي على كل تفاصيل الطلب.
  5. خدمة الطلبات ترد فوراً للواجهة الأمامية بأن الطلب تم استلامه بنجاح. (العملية سريعة جداً!)

الآن، في الخلفية وبشكل غير متزامن (Asynchronously):

  • وسيط الأحداث يرى أن خدمة المخزون وخدمة الإشعارات مشتركتان في هذا الحدث.
  • يرسل نسخة من حدث OrderPlacedEvent إلى خدمة المخزون، التي تقوم بخصم الكمية.
  • يرسل نسخة أخرى من نفس الحدث إلى خدمة الإشعارات، التي تقوم بإرسال الإيميل.

شفتوا الجمال؟ خدمة الطلبات خلصت شغلها بسرعة البرق. ما بتعرف أصلاً بوجود خدمة اسمها “إشعارات” أو “مخزون”. لو خدمة الإشعارات واقعة لمدة ساعة؟ ولا يهم، وسيط الأحداث رح يحتفظ بالرسالة لحد ما خدمة الإشعارات ترجع تشتغل وياخدها. النظام صار مرن وقوي وقادر على تحمل الأخطاء.

شوية كود للتوضيح (Pseudo-code)

هيك ممكن يكون شكل الكود في خدمة الطلبات (المنتج):


// Using a hypothetical library for an event broker
class OrderService {
  
  constructor(private eventBroker) {}

  placeOrder(orderDetails) {
    // 1. Save order to the database
    const newOrder = db.saveOrder(orderDetails);

    // 2. Create the event payload
    const event = {
      name: 'OrderPlacedEvent',
      payload: {
        orderId: newOrder.id,
        customerId: newOrder.customerId,
        items: newOrder.items,
        timestamp: new Date()
      }
    };

    // 3. Publish the event and forget about it
    this.eventBroker.publish('orders.placed', event);

    // 4. Return success to the user immediately
    return { success: true, orderId: newOrder.id };
  }
}

وهيك ممكن يكون شكل الكود في خدمة الإشعارات (المستهلك):


// Using a hypothetical library for an event broker
class NotificationService {

  constructor(private eventBroker) {
    // Subscribe to the event when the service starts
    this.listenForOrderPlacedEvents();
  }

  listenForOrderPlacedEvents() {
    this.eventBroker.subscribe('orders.placed', (event) => {
      console.log(`Received OrderPlacedEvent for order: ${event.payload.orderId}`);
      
      // Send the confirmation email
      this.sendConfirmationEmail(event.payload.customerId, event.payload.orderId);
    });
  }

  sendConfirmationEmail(customerId, orderId) {
    // ... logic to find customer email and send it
    console.log(`Email sent to customer ${customerId} for order ${orderId}.`);
  }
}

الكود مجرد للتوضيح، لكن الفكرة واضحة. فصل تام بين المنتج والمستهلك.

نصائح من خبرة أبو عمر

الانتقال للـ EDA مش بكبسة زر، وفي شوية مطبات تعلمنا منها بالطريقة الصعبة. خذوا هالنصايح من أخوكم:

  1. ابدأ صغيرًا (Start Small): ما في داعي تعيد كتابة كل نظامك مرة وحدة. “شوي شوي يا حبيبي”. اختار منطقة واحدة في نظامك بتعاني من مشاكل الاقتران وابدأ طبق فيها الـ EDA. مثلاً، ابدأ بميزة جديدة أو خدمة واحدة.
  2. المراقبة ثم المراقبة (Observability): في الـ EDA، تتبع مسار عملية كاملة (مثلاً من الطلب للشحن) بصير أصعب لأنه كل شي غير متزامن. لازم تستثمر في أدوات التتبع الموزع (Distributed Tracing) مثل Jaeger أو OpenTelemetry عشان تعرف كل حدث وين راح ومين استلمه وشو عمل فيه. “بدك تعرف وين راحت الرسالة!”.
  3. تعامل مع الفشل بحكمة: شو بصير لو خدمة الإشعارات حاولت تعالج حدث وفشلت (مثلاً بسبب خطأ في الكود)؟ لو ما عملت إشي، وسيط الأحداث رح يضل يحاول يبعتلها نفس الرسالة للأبد. هون بيجي دور مفهوم الـ “طابور الموتى” (Dead-Letter Queue – DLQ). أي رسالة بتفشل معالجتها عدد معين من المرات، بتنتقل تلقائياً للـ DLQ عشان المطورين يشوفوها ويحللوا المشكلة لاحقاً بدون ما توقف النظام كله.
  4. اتفقوا على شكل الأحداث (Schema & Versioning): الأحداث هي العقد (Contract) الجديد بين خدماتك. لازم يكون في اتفاق واضح على شكل كل حدث (Schema). استخدموا أدوات مثل JSON Schema أو Avro. وفكروا من أول يوم كيف رح تطوروا هاي الأحداث في المستقبل (Versioning) بدون ما تكسروا الخدمات القديمة.

الخلاصة

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

هل هي الحل لكل المشاكل؟ طبعاً لا. الـ EDA بتضيف طبقة من التعقيد (وسيط الأحداث) ولازم تفهمها منيح عشان ما تسبب مشاكل جديدة. لكن بالنسبة للأنظمة الكبيرة والمعقدة اللي بتحتاج تكون مرنة ومتاحة دايماً، هي مش مجرد خيار، بل ضرورة.

نصيحتي الأخيرة لكل مطور أو مهندس برمجيات: لا تخاف من تفكيك الروابط الخانقة. اسمح لخدماتك أن تتنفس وتتحدث بهدوء واستقلالية. صدقني، نومك في الليل رح يصير أهدأ بكثير 😉.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

ذكاء اصطناعي

كانت نماذجنا تتقادم في صمت: كيف أنقذتنا ‘مراقبة انحراف النموذج’ (Model Drift) من جحيم القرارات المتدهورة؟

في هذه المقالة، يشارككم أبو عمر قصة من أرض الواقع عن كيفية تدهور أداء نماذج الذكاء الاصطناعي مع مرور الوقت، وهي ظاهرة تُعرف باسم "انحراف...

27 مايو، 2026 قراءة المزيد
خوارزميات

كانت مهامنا تتنفذ بعشوائية: كيف أنقذنا ‘الفرز الطوبولوجي’ من جحيم الاعتماديات المتشابكة؟

في عالم البرمجة، الاعتماديات المتشابكة كابوس حقيقي. تعالوا أحكي لكم قصة كيف أنقذتنا خوارزمية 'الفرز الطوبولوجي' من الفوضى، وحولت مهامنا العشوائية إلى خطة عمل واضحة...

27 مايو، 2026 قراءة المزيد
برمجة وقواعد بيانات

كانت تغييرات قاعدة بياناتنا فوضى عارمة: كيف أنقذتنا أدوات الترحيل (Database Migrations) من جحيم “التعديل اليدوي على الإنتاج”؟

هل تعاني من فوضى تعديل قواعد البيانات يدويًا على خادم الإنتاج؟ في هذه المقالة، أشارككم قصة حقيقية كيف انتقلنا كفريق من جحيم التعديلات اليدوية إلى...

27 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت إعداداتنا السحابية كتاباً مفتوحاً: كيف أنقذتنا أدوات CSPM من جحيم الثغرات الصامتة؟

أشارككم قصة حقيقية من قلب المعركة التقنية، عندما كادت غلطة بسيطة في الإعدادات السحابية أن تكلفنا كل شيء. اكتشفوا كيف كانت أدوات إدارة وضع الأمان...

27 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت إجاباتي في المقابلات السلوكية كارثية: كيف أنقذتني ‘طريقة STAR’ من جحيم ‘حدثنا عن موقف صعب’؟

هل تتجمد عندما يُطلب منك 'الحديث عن موقف صعب' في المقابلات؟ كنت مثلك تمامًا! في هذه المقالة، أشارككم قصتي مع المقابلات السلوكية وكيف حولتني 'طريقة...

27 مايو، 2026 قراءة المزيد
البودكاست