خدماتنا كانت مقيدة ببعضها: كيف أنقذتنا ‘المعمارية الموجهة بالأحداث’ (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) كان نقلة نوعية لمشروعنا وفريقنا. لقد حررنا من قيود الاقتران المحكم وأعطانا نظامًا أكثر مرونة وقابلية للتوسع والتحمل. صحيح أن الطريق لم يكن مفروشًا بالورود، وتطلب منا تعلم مفاهيم جديدة ومواجهة تحديات مختلفة، لكن النتيجة كانت تستحق كل هذا العناء.

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

أبو عمر

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

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

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

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

آخر المدونات

ذكاء اصطناعي

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

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

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

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

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

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

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

أتذكر ليلة كاد فيها تطبيقنا أن ينهار بسبب بطء قاتل، والسبب؟ استعلام بسيط يستغرق دهراً. في هذه المقالة، أشارككم قصة كيف اكتشفنا عدو الأداء الصامت...

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

تطبيقاتنا كانت تستجدي البيانات أو تغرق فيها: كيف أنقذنا GraphQL من جحيم الـ Over-fetching والـ Under-fetching؟

أشارككم قصة حقيقية من تجربتي كمبرمج، وكيف عانينا من مشاكل الأداء بسبب الطريقة التقليدية لجلب البيانات في REST APIs. سأشرح لكم كيف كانت تقنية GraphQL...

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