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

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

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

فتحت الخط، وصوت زميلي من الطرف الثاني كله توتر: “أبو عمر، الحق! النظام واقع! الطلبات الجديدة ما عم تنعمل!”.

مشروعنا كان عبارة عن منصة تجارة إلكترونية واعدة، مبنية على معمارية الخدمات المصغرة (Microservices). على الورق، كل إشي كان تمام. خدمة للمنتجات، خدمة للمستخدمين، خدمة للطلبات، خدمة للدفع، وخدمة للإشعارات. كل خدمة قائمة بذاتها… أو هيك كنت مفكر.

بعد ساعات من الحفر والبحث في الأكواد والـ logs، اكتشفنا الكارثة. خدمة الإشعارات، اللي كل وظيفتها تبعت إيميل للمستخدم لما يشتري شغلة، كانت بتعتمد على خدمة طرف ثالث (3rd party) لإرسال الإيميلات. هاي الخدمة الخارجية توقفت عن العمل بشكل مفاجئ. طيب شو المشكلة؟ المشكلة إنه خدمة “الطلبات” الرئيسية كانت مبرمجة إنها تستدعي خدمة “الإشعارات” بشكل مباشر ومتزامن (Synchronous). فلما خدمة الإشعভাগেরات توقفت عن الاستجابة، خدمة الطلبات صارت تعلق وتعمل timeout، وبالتالي، كل عملية إنشاء طلب جديد في النظام فشلت.

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

ما هي المعمارية الموجهة بالأحداث (EDA)؟ وليش هي الحل؟

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

المعمارية الموجهة بالأحداث بتغير هاي المعادلة كلها. بدل الاتصالات المباشرة، بصير عنا “وسيط” أو “ناقل أحداث” (Message Broker/Event Bus). الخدمات بتبطل تحكي مع بعض مباشرة، وبصير الحوار كالتالي:

  1. المنتج (Producer): خدمة معينة (مثلاً خدمة الطلبات) بتعمل إشي مهم، فبتعلن عن “حدث” (Event) وبترميه في ناقل الأحداث. مثلاً: “يا عالم، تم إنشاء طلب جديد برقم 123!”. بعد ما ترمي الحدث، هي بتكمل شغلها وبتنسى الموضوع.
  2. المستهلك (Consumer): خدمات ثانية بتكون “مشتركة” ومستنية تسمع عن أحداث معينة. خدمة الإشعارات مثلاً، بتكون مشتركة في حدث “تم إنشاء طلب جديد”. لما تسمع الإعلان، بتاخد معلومات الطلب وبتروح تبعت الإيميل براحتها. خدمة المخزون بتسمع نفس الحدث وبتروح بتنقص المنتج من المستودع.

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

العودة إلى “مسرح الجريمة”: كيف طبقنا EDA في نظامنا؟

بعد ليلة الكارثة، قعدنا كفريق وقررنا نعيد هيكلة أهم عملية في النظام: إنشاء الطلب. هيك كان شكلها قبل وبعد.

قبل EDA: الاقتران المحكم (The Tightly Coupled Hell)

كانت خدمة الطلبات (Order Service) تعمل سلسلة من الاستدعاءات المباشرة والمتزامنة:


// Pseudo-code for the OLD Order Service
function createOrder(user, items) {
  // 1. Validate the order
  if (!isValid(user, items)) {
    throw new Error("Invalid order");
  }

  // 2. Call Payment Service (Direct API call)
  const paymentResult = PaymentService.processPayment(user, calculateTotal(items));
  if (!paymentResult.success) {
    throw new Error("Payment failed");
  }

  // 3. Call Inventory Service (Direct API call)
  const inventoryResult = InventoryService.decreaseStock(items);
  if (!inventoryResult.success) {
    // Oh no! We need to refund the payment now (complex rollback logic)
    PaymentService.refund(paymentResult.transactionId);
    throw new Error("Not enough stock");
  }

  // 4. Call Notification Service (Direct API call)
  // THIS IS WHAT FAILED!
  const notificationResult = NotificationService.sendOrderConfirmation(user.email, orderDetails);
  if (!notificationResult.success) {
    // The entire process fails if notifications are down!
    // We might have to rollback payment and inventory again... what a mess.
    throw new Error("Failed to send confirmation email");
  }

  // 5. Finally, save the order
  saveOrderToDatabase(...);
  return "Order created successfully!";
}

شايفين التعقيد؟ أي خطوة بتفشل بتسبب سلسلة من المشاكل والـ Rollbacks، والنظام كله هش.

بعد EDA: فك الارتباط والحرية (The Decoupled Freedom)

أدخلنا وسيط رسائل (استخدمنا RabbitMQ وقتها). شوفوا كيف العملية صارت أبسط وأكثر مرونة في خدمة الطلبات:


// Pseudo-code for the NEW Order Service (with EDA)
async function createOrder(user, items) {
  // 1. Save order with "PENDING" status
  // The service's primary responsibility is just to create the order record.
  const order = await db.orders.save({ user, items, status: 'PENDING' });

  // 2. Create an "OrderPlaced" event
  const event = {
    type: 'OrderPlaced',
    payload: {
      orderId: order.id,
      userId: user.id,
      items: items,
      timestamp: new Date()
    }
  };

  // 3. Publish the event to the message broker (e.g., RabbitMQ)
  await messageBroker.publish('order_events', event);

  // 4. Return success to the user IMMEDIATELY!
  // The user doesn't have to wait for payment, inventory, or notifications.
  return { success: true, message: "Your order is being processed.", orderId: order.id };
}

خدمة الطلبات صار شغلها بسيط جداً: تسجل الطلب وترمي حدث. انتهى. الآن، الخدمات الثانية هي اللي بتشتغل.

خدمة الدفع (Payment Service): تستمع لحدث OrderPlaced. لما يوصلها، بتحاول تسحب المبلغ. إذا نجحت، بترمي حدث جديد اسمه PaymentSuccessful. إذا فشلت، بترمي حدث PaymentFailed.

خدمة المخزون (Inventory Service): تستمع لحدث PaymentSuccessful. لما يوصلها، بتنقص الكمية من المخزون.

خدمة الإشعارات (Notification Service): تستمع لحدث PaymentSuccessful وبتبعت إيميل التأكيد. وبتستمع كمان لحدث PaymentFailed وبتبعت إيميل للمستخدم تعلمه بالمشكلة.

بهذه الطريقة، لو خدمة الإشعارات وقعت، الدفع والمخزون بكملوا شغلهم عادي. ولما خدمة الإشعارات ترجع تشتغل، رح تلاقي كل أحداث PaymentSuccessful مستنياها في الـ Queue (طابور الانتظار) وبتبدأ تعالجهم واحد ورا الثاني.

نصائح أبو عمر الذهبية لتطبيق EDA بنجاح

على مدار السنين، تعلمت كم شغلة مهمة عن هذا الموضوع، وحابب أشارككم إياها:

  • ابدأ بسيطًا: مش ضروري تحول كل نظامك لـ EDA مرة واحدة. ابدأ بعملية واحدة حرجة (like order processing) وحولها. شوف الفوائد وتعلم من التجربة، وبعدين توسع شوي شوي.
  • اختر وسيط الرسائل (Message Broker) المناسب: في خيارات كثيرة زي RabbitMQ, Apache Kafka, AWS SQS/SNS, Google Pub/Sub. كل واحد إله نقاط قوة وضعف. Kafka ممتاز للبيانات الضخمة والمتدفقة (streaming). RabbitMQ مرن جداً في توجيه الرسائل. SQS بسيط ومُدار بالكامل. افهم احتياجك قبل ما تختار، “مش كل إشي بينفع لكل إشي يا جماعة”.
  • صمّم أحداثك بعناية (Event Schema): الحدث هو عقد (contract) بين خدماتك. لازم يكون إله هيكل واضح وموثق. فكر في إضافة رقم إصدار (version) للحدث، عشان لو غيرت هيكله في المستقبل، الخدمات القديمة ما تنضرب. مثلاً OrderPlaced_v2.
  • التعامل مع الفشل (Idempotency & Dead Letter Queues): شو بصير لو خدمة استهلكت حدث، وبدأت تعالجه، بس فشلت في النص؟ الوسيط ممكن يرجع يبعتلها نفس الحدث مرة ثانية. لازم خدمتك تكون مصممة بحيث لو استقبلت نفس الحدث مرتين، ما تعمل العملية مرتين (هذا ما يسمى بـ Idempotency). وإذا فشلت الخدمة في معالجة حدث معين بشكل متكرر، لازم يكون في آلية لنقل هذا الحدث “المسموم” إلى طابور خاص اسمه Dead Letter Queue (DLQ) عشان المطورين يحللوه لاحقاً بدون ما يوقفوا باقي الشغل.
  • المراقبة والرصد (Observability): تتبع مسار حدث معين عبر عدة خدمات ممكن يكون أصعب من تتبع استدعاء مباشر. لازم تستخدم أدوات Tracing (مثل OpenTelemetry) عشان تقدر تشوف رحلة الحدث من المنتج لكل المستهلكين. “بدك تكون شايف كل إشي، زي الصقر، عشان تعرف وين المشكلة إذا صارت”.

الخلاصة: من شبكة عنكبوت إلى أوركسترا متناغمة 🎶

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

نصيحتي الأخيرة لكل مطور ومبرمجة: لا تخافوا من تفكيك الأنظمة المعقدة عشان تبنوها من جديد بشكل أفضل. الاستثمار الأولي في تعلم وتطبيق معماريات مثل EDA رح يوفر عليكم ليالي طويلة من تصليح المشاكل والحرائق في المستقبل. هو استثمار في راحة بالكم وفي نجاح مشاريعكم على المدى الطويل.

يلا، شدّوا حيلكم، والله يوفقكم! 💪

أبو عمر

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

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

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

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

آخر المدونات

أتمتة العمليات

خوادمنا كانت جزرًا معزولة: كيف أنقذني ‘الكود كبنية تحتية’ (IaC) من جحيم التكوين اليدوي؟

أشارككم قصة حقيقية عن ليلة كابوسية انهارت فيها خوادمنا، وكيف كانت هذه الكارثة هي البداية لتبني مفهوم "الكود كبنية تحتية" (IaC). استكشفوا معي كيف حوّل...

5 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

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

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

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

استعلاماتي كانت تزحف كالسلحفاة: كيف أنقذتني ‘فهارس قاعدة البيانات’ (Database Indexes) من جحيم الانتظار الطويل؟

أشارككم قصتي مع استعلام SQL استغرق دقائق ليُنفّذ، وكيف تحول إلى أجزاء من الثانية بفضل الفهارس (Indexes). سنغوص في عالم فهارس قواعد البيانات، من هي؟...

5 أبريل، 2026 قراءة المزيد
الشبكات والـ APIs

خدماتي المصغرة كانت فوضى: كيف أنقذتني ‘بوابة الواجهات البرمجية’ (API Gateway) من جحيم الإدارة؟

أشارككم تجربتي الشخصية مع فوضى إدارة الخدمات المصغرة (Microservices) وكيف كانت بوابة الواجهات البرمجية (API Gateway) هي المنقذ الذي أعاد النظام والمنطق إلى بنيتي التحتية....

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

فواتيري السحابية كانت تحرق ميزانيتي: كيف أنقذتني ‘علامات الموارد’ (Resource Tags) من جحيم التكاليف الخفية؟

أنا أبو عمر، وأشارككم اليوم قصتي مع فواتير الحوسبة السحابية التي كادت أن تخرج عن السيطرة. اكتشفوا كيف أن أداة بسيطة مثل "علامات الموارد" (Resource...

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

ملفي الشخصي كان مجرد مستودع أكواد صامت: كيف حوّلني ‘GitHub Profile README’ إلى مغناطيس للفرص؟

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

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