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

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

بتذكر هذاك اليوم زي كأنه مبارح. كانت ليلة خميس، والكل بستنى الويكند على نار. فجأة، بدأت توصلني التنبيهات على الموبايل زي المطر… “System is down!”، “Users can’t register!”، “API is throwing 500 errors!”. نزلت ركض على المكتب، وفنجان القهوة بإيدي اللي كانت بترجف، وبديت رحلة البحث عن السبب.

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

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

ما هي المعمارية المتشابكة (Tightly Coupled)؟ أو “كُبّة الصوف” كما أسميها

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

في عالم البرمجيات، هذا يعني أن الخدمة (أ) تستدعي الخدمة (ب) عبر طلب HTTP API مباشر وتنتظر الرد. هذا الارتباط المباشر يخلق عدة مشاكل قاتلة:

  • هشاشة النظام (Fragility): كما حدث في قصتي، أي فشل أو بطء في خدمة واحدة (مثل خدمة الإشعارات) يمكن أن يتسبب في سلسلة من الأعطال (Cascading Failures) تشل النظام بأكمله.
  • صعوبة التطوير (Development Difficulty): عندما تريد تعديل شيء في الخدمة (ب)، عليك أن تكون حذرًا جدًا حتى لا تكسر شيئًا في الخدمة (أ) التي تعتمد عليها. بدك تغير شغلة صغيرة، بتلاقي حالك بتفحص كل النظام. هذا يقتل سرعة التطوير والابتكار.
  • محدودية التوسع (Scalability Issues): لنفترض أن خدمة المستخدمين تتلقى 1000 طلب في الثانية، بينما خدمة الإشعارات لا تستطيع معالجة أكثر من 100 طلب في الثانية. في النموذج المتشابك، ستصبح خدمة الإشعارات عنق الزجاجة (Bottleneck) الذي يحد من قدرة النظام بأكمله على التوسع.

الحل السحري: المعمارية الموجهة بالأحداث (Event-Driven Architecture – EDA)

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

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

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

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

  1. الحدث (Event): هو سجل يوثق حقيقة وقوع شيء ما في الماضي. على سبيل المثال، UserRegistered, OrderPlaced, PaymentProcessed. يحتوي الحدث على البيانات اللازمة لوصف ما حدث (مثل هوية المستخدم، بريده الإلكتروني، قيمة الطلب، إلخ).
  2. المنتِج (Producer/Publisher): هو الخدمة التي تكتشف الحدث وتنشئه وتنشره. في مثالنا، “خدمة المستخدمين” هي المنتِج لحدث UserRegistered.
  3. المستهلِك (Consumer/Subscriber): هو الخدمة التي تستمع إلى الأحداث وتتفاعل معها. في مثالنا، “خدمة الإشعارات” و”خدمة التحليلات” هما مستهلكان.
  4. ناقل الأحداث (Event Broker/Bus): هو الوسيط أو “لوحة الإعلانات”. إنه البنية التحتية التي تستقبل الأحداث من المنتجين وتوجهها إلى المستهلكين المهتمين. من أشهر الأمثلة: RabbitMQ, Apache Kafka, AWS SQS/SNS, Google Pub/Sub.

تطبيق عملي: كيف أعدنا بناء نظامنا باستخدام EDA

دعونا نعود إلى سيناريو تسجيل المستخدم الذي كاد أن يدمر شركتنا ونرى كيف أصلحناه باستخدام EDA.

السيناريو القديم (كُبّة الصوف)

1. المستخدم يرسل طلب تسجيل إلى خدمة المستخدمين.

2. خدمة المستخدمين تحفظ بياناته في قاعدة البيانات.

3. خدمة المستخدمين تستدعي خدمة الإشعارات مباشرة (API Call) وتنتظر…

4. (هنا حدثت المشكلة: خدمة الإشعارات بطيئة جدًا أو معطلة).

5. خدمة المستخدمين تبقى معلقة، مما يؤدي إلى انتهاء مهلة الطلب (Timeout) وفشل عملية التسجيل بأكملها.

السيناريو الجديد (سيمفونية EDA)

1. المستخدم يرسل طلب تسجيل إلى خدمة المستخدمين.

2. خدمة المستخدمين تحفظ بياناته في قاعدة البيانات.

3. خدمة المستخدمين تنشئ حدثًا اسمه UserRegistered يحتوي على تفاصيل المستخدم (ID, email, name).

4. خدمة المستخدمين ترسل هذا الحدث إلى ناقل الأحداث (Event Broker) وتنسى أمره تمامًا.

5. خدمة المستخدمين ترد فورًا على المستخدم بنجاح العملية “تم التسجيل بنجاح!”. (تجربة مستخدم أسرع وأفضل بكثير!)

— في الخلفية، وبشكل غير متزامن —

6. خدمة الإشعارات، المشتركة في حدث UserRegistered، تستلم الحدث من الناقل وترسل البريد الترحيبي.

7. خدمة التحليلات، المشتركة أيضًا في نفس الحدث، تستلمه وتحدث مؤشراتها.

8. خدمة جديدة (مثلاً خدمة التوصيات) يمكن إضافتها للاستماع لنفس الحدث دون الحاجة لتعديل سطر واحد في “خدمة المستخدمين”.

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

مثال بالكود: لنكتب شيئًا بسيطًا (Python و RabbitMQ)

الكلام النظري جميل، لكن دعونا نرى كيف يبدو هذا على أرض الواقع. سنستخدم Python و RabbitMQ كمثال.

المنتج (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')

# بيانات الحدث
event_data = {
    'event_type': 'UserRegistered',
    'user_id': 'usr_12345',
    'email': 'ahlan@example.com',
    'name': 'أبو عمر'
}

# نشر الحدث
channel.basic_publish(
    exchange='user_events',
    routing_key='',  # routing_key لا يهم في نوع fanout
    body=json.dumps(event_data)
)

print(" [x] Sent 'UserRegistered' event")
connection.close()

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

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


import pika
import json
import time

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

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

# تعريف queue مؤقتة (اسمها يحدده RabbitMQ)
# exclusive=True تعني أن الـ 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):
    event_data = json.loads(body)
    if event_data.get('event_type') == 'UserRegistered':
        print(f" [->] Received UserRegistered event for user: {event_data.get('email')}")
        print(" [!] Simulating sending a welcome email...")
        time.sleep(2) # محاكاة لعملية إرسال البريد
        print(" [✔] Email sent successfully.")
    
    # إعلام RabbitMQ بأن الرسالة تمت معالجتها
    ch.basic_ack(delivery_tag=method.delivery_tag)

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

channel.start_consuming()

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

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

  • ابدأ صغيرًا: لا تهدم كل الدار وتبنيها من جديد. اختر عملية واحدة غير حرجة في نظامك (مثل تحديث صورة الملف الشخصي) وحولها إلى EDA. تعلم من التجربة ثم توسع تدريجيًا. خذها حبة حبة.
  • صمم أحداثك بعناية: عقد الحدث (Event Schema) هو واجهة برمجة التطبيقات الجديدة بين خدماتك. اجعله غنيًا بالمعلومات الكافية للمستهلكين، وفكر في كيفية إدارة الإصدارات المختلفة منه منذ اليوم الأول (Schema Versioning).
  • اختر ناقل الأحداث المناسب: مش كل إشي بده مدفع يا جماعة. Kafka ممتاز للتدفقات الضخمة والتحليلات الفورية. RabbitMQ رائع للتوجيه المعقد وقوائم المهام. خدمات سحابية مثل AWS SQS/SNS بسيطة وممتازة لفك الارتباط الأولي بين الخدمات. اختر الأداة التي تناسب مشكلتك.
  • فكر في قابلية المراقبة (Observability): تصحيح الأخطاء في نظام موزع قد يكون كابوسًا. أنت بحاجة ماسة إلى أدوات تتبع (Tracing) مثل OpenTelemetry لترى رحلة الحدث عبر الخدمات المختلفة، بالإضافة إلى سجلات (Logging) ومراقبة (Monitoring) جيدة.
  • تعامل مع الفشل بأناقة: ماذا لو فشل المستهلك في معالجة حدث؟ لا تدعه يختفي. استخدم نمط “قائمة انتظار الرسائل الميتة” (Dead Letter Queue – DLQ) لإرسال الأحداث الفاشلة إلى مكان آمن لتحليلها وإعادة معالجتها لاحقًا.
  • تقبّل الاتساق النهائي (Eventual Consistency): في عالم EDA، البيانات لا تتحدث فورًا في كل مكان. لوحة التحليلات لن تُظهر المستخدم الجديد في نفس الثانية التي يسجل فيها. ستكون “متسقة في النهاية”. هذا هو الثمن الذي تدفعه مقابل المرونة وقابلية التوسع، وعليك أن تكون واعيًا به وتصمم نظامك بناءً عليه.

الخلاصة: من فوضى كُبّة الصوف إلى سيمفونية متناغمة 🎼

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

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

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

أبو عمر

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

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

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

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

آخر المدونات

برمجة وقواعد بيانات

تحديثات قاعدة البيانات بدون توقف: كيف أنقذنا نمط التوسيع والتعاقد (Expand/Contract) من جحيم التوقفات المجدولة؟

هل سئمت من إيقاف الخدمة مع كل تحديث لهيكلة قاعدة البيانات؟ أشارككم قصة حقيقية وكيف أنقذنا نمط التوسيع والتعاقد (Expand/Contract) من ليالي النشر الطويلة والمُجهدة،...

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

كانت إعادة المحاولة كارثة: كيف أنقذتنا مفاتيح عدم تكرار العمليات (Idempotency Keys) من جحيم الفواتير المزدوجة؟

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

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

من التوقف التام إلى النجاة: كيف أنقذتنا استراتيجية “الضوء المرشد” (Pilot Light) يوم انقطعت السحابة؟

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

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

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

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

4 يونيو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

من الانتظار لأيام إلى الدفع في ثوانٍ: كيف أنقذتنا شبكات الدفع الفوري من جحيم التحويلات البنكية؟

أسرد لكم من واقع تجربتي كـ "أبو عمر"، كيف عانينا من بطء وتكلفة التحويلات البنكية الدولية، وكيف جاءت شبكات الدفع الفوري ومعيار ISO 20022 لتكون...

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

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

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

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

كانت تغطية الاختبارات 100% لكن الأخطاء تتسرب: كيف أنقذنا “الاختبار الطفري” من جحيم الثقة الزائفة؟

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

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