من جحيم التبعيات إلى نعيم الاستقلالية: رحلتي مع المعمارية القائمة على الأحداث (EDA)

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

التحديث كان في خدمة المستخدمين (Users Service)، مجرد إضافة حقل جديد في بروفايل المستخدم. ضغطنا زر النشر (Deploy) واحنا متطمنين. وما هي إلا دقايق، وإجا التلفون اللي بنكرهه كلنا… تلفون من فريق الدعم الفني. “أبو عمر، نظام الطلبات واقع! والزبائن مش قادرين يعملوا طلبات جديدة!”.

قلبي وقع بين رجليي. شو دخل خدمة المستخدمين بخدمة الطلبات (Orders Service)؟ ركضنا نفحص السجلات (Logs)، وإذ بالمصيبة: خدمة الطلبات، عشان تتأكد من بيانات المستخدم، كانت بتستدعي خدمة المستخدمين بشكل مباشر ومتزامن (Synchronous). التحديث الجديد اللي عملناه سبب بطء بسيط في الاستجابة، وهذا البطء كان كافي إنه يسبب “Timeouts” في خدمة الطلبات، وبالتالي… انهيار كامل للعملية. قضينا ليلتها للصبح واحنا بنعمل تراجع (Rollback) وبنصلّح المشكلة. هذيك الليلة، قررت إنه “خلص، بكفي! لازم نلاقي حل جذري لهي الكُبّة (الكرة المتشابكة) من التبعيات”.

الكابوس: الاقتران الشديد (Tight Coupling)

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

أعراض هذا المرض الخبيث

  • شلالات الفشل (Cascading Failures): زي ما صار معنا، فشل خدمة واحدة يؤدي إلى فشل خدمات أخرى تباعاً.
  • بطء التطوير: كل فريق بصير يخاف يعمل أي تغيير لأنه مش عارف شو ممكن يخرب في الخدمات الثانية. الاجتماعات بتطول والتنسيق بصير كابوس.
  • صعوبة التوسع (Scaling): إذا كانت خدمة الطلبات عليها ضغط عالي، فأنت مضطر تعمل توسيع (Scale out) الها ولخدمة المستخدمين اللي هي معتمدة عليها، حتى لو خدمة المستخدمين ما عليها ضغط. هذا هدر للموارد.
  • رعب يوم النشر (Deployment Dread): بصير يوم نشر التحديثات يوم حزين، مليان توتر ودعاء إنه ما تصير مصايب.

الفجر الجديد: المعمارية القائمة على الأحداث (EDA)

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

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

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

  1. منتج الحدث (Event Producer): هو الخدمة اللي “بتعلن الخبر”. في مثالنا، خدمة المستخدمين هي المنتج.
  2. موجّه الأحداث (Event Broker/Router): هو “المنصة” أو “لوحة الإعلانات العامة”. هو وسيط بستلم الأحداث من المنتجين وبوزعها للمستهلكين المهتمين. أمثلة عليه: RabbitMQ, Apache Kafka, AWS SQS.
  3. مستهلك الحدث (Event Consumer): هو الخدمة اللي “بتسمع الخبر” وبتتفاعل معه. في مثالنا، خدمة الإشعارات ممكن تكون مستهلك.

كيف حررتنا EDA؟ تطبيق عملي

خلونا نرجع لسيناريو تحديث بروفايل المستخدم، بس هالمرة باستخدام EDA.

  1. خدمة المستخدمين (Users Service) لم تعد تسمح لأحد باستدعائها مباشرة. بدلاً من ذلك، عندما يقوم مستخدم بتحديث ملفه الشخصي، تقوم الخدمة بإنتاج حدث (Event) اسمه UserProfileUpdated.
  2. يتم إرسال هذا الحدث إلى وسيط الرسائل (Message Broker) مثل RabbitMQ.
  3. خدمة الإشعارات (Notifications Service) تكون “مشتركة” ومستمعة لهذا النوع من الأحداث. عندما يصلها الحدث، تقوم بإرسال إيميل للمستخدم “تم تحديث ملفك الشخصي بنجاح”.
  4. خدمة التحليلات (Analytics Service) قد تكون مشتركة أيضاً بنفس الحدث لتسجيل نشاط المستخدم.
  5. والأهم: خدمة الطلبات (Orders Service)؟ لا هي منتج ولا مستهلك لهذا الحدث. هي ببساطة “مش مهتمة”. لم تعد تعرف بوجود تحديث في خدمة المستخدمين أصلاً.

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

مثال كود بسيط (بايثون مع RabbitMQ)

لتقريب الفكرة، هذا مثال مبسط جداً باستخدام مكتبة pika في بايثون.

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


import pika
import json

# الاتصال بـ RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# تعريف exchange من نوع fanout (يرسل لكل الـ queues المرتبطة به)
channel.exchange_declare(exchange='user_events', exchange_type='fanout')

user_data = {'user_id': 123, 'new_email': 'omar.new@example.com'}
message = json.dumps(user_data)

# نشر الحدث
channel.basic_publish(exchange='user_events', routing_key='', body=message)

print(f" [x] Sent event: UserProfileUpdated for user {user_data['user_id']}")
connection.close()

المستهلك (Consumer – في خدمة الإشعارات)


import pika
import json

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='user_events', exchange_type='fanout')

# إنشاء queue عشوائية مؤقتة
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue

# ربط الـ queue بالـ exchange
channel.queue_bind(exchange='user_events', queue=queue_name)

print(' [*] Waiting for user events. To exit press CTRL+C')

def callback(ch, method, properties, body):
    user_data = json.loads(body)
    print(f" [!] Received UserProfileUpdated event for user {user_data['user_id']}")
    print(f"     -> Sending notification email to {user_data['new_email']}...")
    # هنا يتم وضع كود إرسال الإيميل الفعلي
    print(" [✔] Notification sent.")


channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)

channel.start_consuming()

لاحظ كيف أن المنتج والمستهلك لا يعرفان أي شيء عن بعضهما البعض. كل ما يجمعهما هو اسم الـ exchange والاتفاق الضمني على شكل البيانات داخل الحدث.

نصائح من كيس أبو عمر

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

  • ابدأ صغيراً: لا تحاول تحويل كل نظامك لـ EDA مرة واحدة. اختر عملية واحدة غير حرجة (non-critical) وحولها كبداية. تعلم منها ثم توسع.
  • عرّف أحداثك بوضوح (Event Schema): اتفق مع فريقك على هيكلية واضحة للأحداث. استخدموا أدوات مثل JSON Schema أو Avro لضمان التوافق وتجنب المشاكل مستقبلاً. الحدث هو عقد (Contract) بين الخدمات، فاجعله واضحاً.
  • فكر في الـ Idempotency: ماذا لو استلمت خدمة الإشعارات نفس الحدث مرتين عن طريق الخطأ؟ هل سترسل إيميلين؟ يجب أن تكون خدماتك المستهلكة قادرة على التعامل مع هذا الموقف بذكاء (Idempotent Consumer). مثلاً، تتأكد من رقم الحدث قبل معالجته.
  • المراقبة ثم المراقبة: في الأنظمة المتزامنة، تتبع الخطأ سهل (Call Stack). في EDA، الأمور غير مباشرة. أنت بحاجة ماسة لأدوات مراقبة (Monitoring) وتتبع (Tracing) قوية لتتمكن من تتبع رحلة الحدث عبر النظام ومعرفة أين حدثت المشكلة.
  • ليست حلاً لكل شيء: الـ EDA ليست المطرقة الذهبية. إذا كنت تحتاج إلى استجابة فورية ومتزامنة (مثلاً، التحقق من توفر اسم مستخدم أثناء التسجيل)، فإن استدعاء API مباشر قد يكون أفضل وأبسط. استخدم الأداة المناسبة للمشكلة المناسبة.

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

الانتقال إلى المعمارية القائمة على الأحداث كان نقلة نوعية لفريقنا ولشركتنا. حولنا من حالة الخوف والترقب عند كل تحديث، إلى حالة من الثقة والمرونة. لم نعد نبني بيوتاً من ورق، بل صرنا نبني شبكة من الخدمات المستقلة والقوية، كل واحدة تؤدي دورها بكفاءة بدون أن تعرقل الأخرى.

الرحلة لم تكن سهلة، وتطلبت تغييراً في طريقة تفكيرنا كمطورين، لكن النتيجة كانت تستحق كل التعب. إذا كنت تعاني من جحيم التبعيات، فربما حان الوقت لتفكر في “إعلان” استقلالك والبدء في رحلتك مع عالم الأحداث. الشغلة مش بس كود، الشغلة تغيير عقلية نحو بناء أنظمة أكثر نضجاً وقوة. 👍

أبو عمر

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

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

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

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

آخر المدونات

نصائح برمجية

كانت شفرتي هرمًا من الشروط المتداخلة: كيف أنقذتني ‘شروط الحماية’ (Guard Clauses) من جحيم الـ Arrow Code؟

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

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

كانت مراجعات المستخدمين صراخاً في الظلام: كيف أنقذنا ‘تحليل المشاعر’ من جحيم تجاهل صوت العميل؟

في عالم تتكدس فيه آراء المستخدمين بالآلاف، يصبح تجاهلها جحيماً حقيقياً. أسرد لكم قصتي كـ "أبو عمر"، وكيف تحولنا من ضياع تام في بحر المراجعات...

24 مايو، 2026 قراءة المزيد
تسويق رقمي

ميزانية التسويق كانت ثقباً أسود: كيف أنقذتنا ‘نماذج الإحالة المبنية على البيانات’ من جحيم التخمين؟

أتذكر جيداً ذلك الاجتماع الذي كاد أن يدمر معنويات فريقنا. كنا ننفق آلاف الدولارات على التسويق الرقمي، لكن العائد كان محيراً والميزانية أشبه بثقب أسود...

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

كانت واجهاتنا متحفاً للفوضى: كيف أنقذنا ‘نظام التصميم’ (Design System) من جحيم التناقض البصري؟

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

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