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

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

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

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

جحيم الاقتران المحكم (The Hell of Tight Coupling)

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

تخيل عندك خدمة تسجيل المستخدمين (UserService)، وخدمة الإشعارات (NotificationService)، وخدمة تحليل البيانات (AnalyticsService). في تصميمنا القديم، كانت الأمور تسير كالتالي:

  1. المستخدم يسجل حسابًا جديدًا.
  2. الـ UserService تحفظ بياناته.
  3. الـ UserService تستدعي (API Call) مباشرة للـ NotificationService لإرسال إيميل ترحيبي.
  4. الـ UserService تنتظر ردًا من NotificationService.
  5. ثم تستدعي الـ UserService مباشرة الـ AnalyticsService لتسجيل هذا الحدث.
  6. الـ UserService تنتظر ردًا من AnalyticsService.
  7. أخيرًا، تُرجع الـ UserService ردًا للمستخدم بأنه تم التسجيل بنجاح.

شايفين المشكلة؟ الـ UserService صار مثل “رئيس القسم المتسلط” اللي لازم يعرف كل صغيرة وكبيرة، ولازم كل إشي يمر من خلاله. هذا التصميم له عواقب وخيمة:

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

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

بعد الكارثة اللي صارت، جلسنا وفكرنا: “لازم نلاقي طريقة تخلي الخدمات تتواصل مع بعضها بدون ما تعرف بعضها مباشرة”. وهنا كان الحل في الـ EDA.

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

كيف يعمل هذا النظام الجديد؟

دعونا نعيد سيناريو تسجيل المستخدم باستخدام EDA:

  1. المستخدم يسجل حسابًا جديدًا.
  2. الـ UserService تحفظ بياناته في قاعدة البيانات الخاصة بها.
  3. الـ UserService تنشر حدثًا اسمه UserRegistered إلى وسيط الرسائل (Message Broker). هذا الحدث يحتوي على معلومات مثل userId و email.
  4. فورًا، تُرجع الـ UserService ردًا للمستخدم بأنه تم التسجيل بنجاح. (لاحظ السرعة هنا!)
  5. في الخلفية، وبشكل غير متزامن (Asynchronously):
    • الـ NotificationService، التي كانت تستمع لأحداث UserRegistered، تستلم الحدث وترسل الإيميل الترحيبي.
    • الـ AnalyticsService، التي تستمع أيضًا لنفس الحدث، تستلمه وتحدث بياناتها التحليلية.
    • يمكن إضافة خدمة جديدة، مثلاً ProfileSetupService، لتستمع لنفس الحدث وتقوم بإنشاء ملف شخصي افتراضي للمستخدم، كل هذا بدون أي تغيير في الـ UserService الأصلية!

لاحظ الفرق الجوهري: الـ UserService لم يعد يهتم بمن يستمع. وظيفته انتهت بمجرد إعلانه عن الحدث. هذا ما نسميه “الفصل” أو “اللامركزية” (Decoupling).

مثال برمجي بسيط (Pseudocode)

لتقريب الصورة، تخيل أن لدينا وسيط رسائل مثل RabbitMQ أو Kafka. الكود قد يبدو هكذا (هذا مثال توضيحي بلغة Python):

في خدمة المستخدمين (UserService – The Producer)


# import message_broker_library

class UserService:
    def register_user(self, name, email, password):
        # 1. Save user to our own database
        user = db.save_user(name, email, password)
        print("User saved to database.")

        # 2. Create an event payload
        event_payload = {
            "event_type": "UserRegistered",
            "data": {
                "user_id": user.id,
                "email": user.email,
                "name": user.name,
                "timestamp": datetime.utcnow().isoformat()
            }
        }

        # 3. Publish the event to a topic/channel called 'user_events'
        message_broker_library.publish(topic="user_events", message=event_payload)
        print("Published UserRegistered event.")

        # 4. Return success to the user immediately
        return {"status": "success", "message": "Registration successful!"}

في خدمة الإشعارات (NotificationService – The Consumer)


# import message_broker_library

def handle_user_registered_event(event_payload):
    user_data = event_payload["data"]
    print(f"Received UserRegistered event for user: {user_data['name']}")
    
    # Send the welcome email
    send_welcome_email(to=user_data['email'], name=user_data['name'])
    print(f"Welcome email sent to {user_data['email']}.")


# This function runs in the background, continuously listening for messages
def start_listening():
    print("NotificationService is listening for events on 'user_events' topic...")
    message_broker_library.subscribe(
        topic="user_events",
        event_type="UserRegistered",
        callback=handle_user_registered_event
    )

لاحظ كيف أن UserService لا يحتوي على أي كود يتعلق بإرسال الإيميلات. هو فقط “يصرخ” في الفراغ بأن مستخدمًا جديدًا قد سجل، ومن يهتم بهذا الخبر، فليتصرف!

الفوائد التي جنيناها (والتحديات أيضًا)

الانتقال إلى EDA لم يكن مجرد تغيير تقني، بل كان تغييرًا في طريقة تفكيرنا كفريق.

الفوائد (الجانب المشرق)

  • المرونة والتحمل (Resilience): إذا توقفت خدمة الإشعارات، هل تتوقف عملية التسجيل؟ لا! الحدث يبقى في “قائمة الانتظار” (Queue) لدى وسيط الرسائل، وعندما تعود خدمة الإشعارات للعمل، تستهلك الحدث وترسل الإيميل وكأن شيئًا لم يكن.
  • قابلية التوسع (Scalability): أصبح بإمكاننا توسيع نطاق كل خدمة على حدة. إذا كان لدينا ضغط على إرسال الإشعارات، نزيد عدد نُسخ (instances) خدمة الإشعارات فقط، دون المساس بباقي النظام.
  • استقلالية الفرق (Team Autonomy): فريق خدمة التحليلات يمكنه العمل والتطوير والنشر بدون الحاجة للتنسيق مع فريق المستخدمين، طالما أنهم متفقون على شكل (schema) حدث UserRegistered.
  • استجابة أسرع للمستخدم: العمليات الطويلة أصبحت تتم في الخلفية، والمستخدم يحصل على استجابة شبه فورية.

نصائح أبو عمر (مش كل إشي وردي!)

يا جماعة، هذا الحل ليس “حبة سحرية” تحل كل المشاكل. هناك تحديات يجب أن تكونوا على دراية بها، وهذه خلاصة خبرتي فيها:

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

  • التعقيد الإضافي: أنت الآن تضيف مكونًا جديدًا وحرجًا للنظام: وسيط الرسائل (Message Broker). هذا الوسيط يحتاج إلى إدارة، ومراقبة، وصيانة. إذا توقف هو، توقف كل شيء.
  • الاتساق النهائي (Eventual Consistency): البيانات لا تتحدث في كل الخدمات بنفس اللحظة. قد يمر جزء من الثانية (أو أكثر) بين تسجيل المستخدم وظهور بياناته في نظام التحليلات. هذا يسمى “الاتساق النهائي”، وهو تغيير كبير عن “الاتساق الفوري” الذي اعتدنا عليه. يجب أن يكون تصميمك قادراً على التعامل مع هذا التأخير.
  • صعوبة التتبع والمراقبة (Debugging & Monitoring): تتبع مسار طلب واحد عبر عدة خدمات منفصلة أصبح أصعب. من الضروري استخدام أدوات وتقنيات مثل (Correlation IDs) لربط الأحداث التي تنتمي لنفس العملية.
  • إدارة مخطط الحدث (Event Schema): ماذا لو أردت إضافة حقل جديد لحدث UserRegistered؟ كيف ستتعامل الخدمات القديمة التي لا تعرف هذا الحقل؟ هنا تظهر الحاجة لتقنيات مثل تحديد إصدارات الأحداث (Event Versioning) واستخدام (Schema Registry).

الخلاصة… والزبدة

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

نصيحتي الأخيرة لكل مطور أو مهندس برمجيات: لا تخف من “تفكيك” نظامك. أحيانًا، أفضل طريقة لتقوية شيء ما هي كسر الروابط الضعيفة التي تقيده، وبناؤها من جديد على أساس أقوى وأكثر مرونة. المعمارية الموجهة بالأحداث هي واحدة من أقوى الأدوات في صندوقك لتحقيق ذلك. فكّوا هالكربجة، وحرروا خدماتكم! 🚀

أبو عمر

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

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

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

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

آخر المدونات

التكنلوجيا المالية Fintech

كانت قراراتنا الائتمانية صندوقاً أسود: كيف أنقذنا ‘الذكاء الاصطناعي القابل للتفسير’ (XAI) من جحيم التحيز والشكاوى التنظيمية؟

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

16 مايو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كانت أعطالنا تباغتنا في منتصف الليل: كيف أنقذنا Prometheus من جحيم المراقبة التفاعلية؟

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

16 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

طلبات الدمج تموت في الانتظار: كيف أنقذ “ميثاق مراجعة الكود” فريقنا من جحيم التأخير والجدل؟

أتذكر ذلك اليوم جيداً، طلب دمج (Pull Request) عالق لأسبوع، ونقاش حاد بين اثنين من أفضل المبرمجين حول تفصيل بسيط. كانت هذه هي القشة التي...

16 مايو، 2026 قراءة المزيد
اختبارات الاداء والجودة

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

أشارككم قصة حقيقية من قلب المعركة البرمجية، وكيف تحولنا من فوضى الأخطاء المرئية بعد كل تحديث إلى ثقة وهدوء بفضل اختبارات التراجع البصري (Visual Regression...

16 مايو، 2026 قراءة المزيد
أتمتة العمليات

كان مطورنا الجديد ينتظر أياماً: كيف أنقذتنا ‘أتمتة إعداد البيئة’ من جحيم الأسبوع الأول الضائع؟

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

15 مايو، 2026 قراءة المزيد
نصائح برمجية

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

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

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

كانت خدماتنا تتحدث في نفس الوقت: كيف أنقذتنا ‘المعمارية القائِمَة على الأحداث’ (EDA) من جحيم الاقتران المحكم؟

في ليلة إطلاق عصيبة، كادت خدماتنا المترابطة أن تُغرق المشروع بأكمله. أروي لكم كيف تحولنا من فوضى الاقتران المحكم إلى مرونة المعمارية القائمة على الأحداث...

15 مايو، 2026 قراءة المزيد
ذكاء اصطناعي

كانت نماذجنا تموت بصمت: كيف أنقذتنا ‘مراقبة تعلم الآلة’ (ML Monitoring) من كارثة التنبؤات الفاسدة؟

أشارككم قصة حقيقية من الميدان، حين كادت نماذج الذكاء الاصطناعي التي بنيناها بجهد أن تنهار بصمت. اكتشفوا معنا ما هي "مراقبة تعلم الآلة" (ML Monitoring)،...

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