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

“وقف كل شي!”… صرخة أيقظتني من كابوس الاقتران المحكم

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

يا زلمة، قلبي نزل عند ركبي. فتحت لوحة المراقبة (Dashboard) بسرعة، وإذ بي أرى الكارثة. خدمة الإشعارات (Notification Service) واقعة بسبب مشكلة بسيطة في الـ API تبع مزود خدمة الرسائل القصيرة (SMS). لكن المصيبة ما كانت هون. المصيبة إنه خدمة الطلبات (Order Service) كانت بتستنى رد من خدمة الإشعارات عشان تكمل شغلها. وبما إنها ما بترد، فالطلبات كلها معلقة في طابور طويل لا ينتهي. تأثير الدومينو بعينه، حجر صغير وقع، ووراه وقعت كل الأحجار الكبيرة.

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

ما هو الجحيم الذي كنت أعيش فيه؟ (مشكلة الاقتران المحكم)

قبل ما نحكي عن الحل، خلينا نفهم أصل المشكلة. النظام اللي بنيته في البداية كان يعتمد على ما يسمى بالـ “الاقتران المحكم” (Tightly Coupled). ببساطة، الخدمات كانت بتتواصل مع بعضها بشكل مباشر ومتزامن (Synchronous).

تخيل معي السيناريو التالي عند إنشاء طلب جديد:

  1. خدمة الطلبات (Order Service) تستقبل الطلب.
  2. تقوم بمناداة مباشرة لخدمة الدفع (Payment Service) وتنتظر منها رداً: “تم الدفع بنجاح”.
  3. بعد استلام الرد، تقوم بمناداة مباشرة لخدمة المخزون (Inventory Service) وتنتظر منها رداً: “تم تحديث المخزون”.
  4. بعد استلام الرد، تقوم بمناداة مباشرة لخدمة الإشعارات (Notification Service) وتنتظر منها رداً: “تم إرسال الإشعار”.
  5. أخيراً، ترد على المستخدم بأن طلبه قد اكتمل.

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

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

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

هنا يأتي دور المنقذ. المعمارية القائمة على الأحداث (Event-Driven Architecture أو EDA) هي طريقة لتصميم الأنظمة بحيث تتواصل الخدمات مع بعضها بشكل غير مباشر وغير متزامن (Asynchronous).

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

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

لفهم هذا النموذج، تخيل وجود “لوحة إعلانات عامة” في نظامك. هذه اللوحة هي ما نسميه وسيط الأحداث (Event Broker).

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

إعادة بناء النظام: القصة بعد تطبيق EDA

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

  1. المستخدم ينشئ طلباً.
  2. خدمة الطلبات (Order Service) تستقبل الطلب، تحفظه في قاعدة بياناتها بحالة “قيد المعالجة”، ثم تنشر فوراً حدثاً اسمه OrderPlaced على وسيط الأحداث (مثلاً، RabbitMQ).
  3. مباشرةً، ترد خدمة الطلبات على المستخدم: “تم استلام طلبك بنجاح، وسنخبرك بالتحديثات”. (لاحظ السرعة! لم ننتظر أي خدمة أخرى).

الآن، في الخلفية، وبشكل مستقل تماماً، يحدث التالي:

  • خدمة الدفع (Payment Service): كانت “تستمع” لحدث OrderPlaced. عندما استلمته، قامت بمعالجة الدفع. بعد الانتهاء، نشرت حدثاً جديداً: إما PaymentSuccessful أو PaymentFailed.
  • خدمة المخزون (Inventory Service): كانت “تستمع” لحدث PaymentSuccessful. عندما استلمته، قامت بخصم الكمية من المخزون.
  • خدمة الإشعارات (Notification Service): كانت “تستمع” لعدة أحداث، منها PaymentSuccessful و PaymentFailed. عندما استلمت أياً منها، قامت بإرسال بريد إلكتروني أو رسالة SMS مناسبة للزبون.

هل ترى الجمال في هذا التصميم؟ الخدمات أصبحت “مفككة الاقتران” (Decoupled). خدمة الطلبات لا تعرف شيئاً عن خدمة الإشعارات أو المخزون. هي فقط تعلن عن حدث، ومن يهتم فليفعل ما يشاء.

لو تعطلت خدمة الإشعارات الآن؟ لا مشكلة على الإطلاق! سيبقى حدث PaymentSuccessful في قائمة الانتظار (Queue) لدى وسيط الأحداث. وعندما تعود خدمة الإشعارات للعمل، ستسحب الحدث وتعالجه وكأن شيئاً لم يكن. النظام أصبح مرناً وقوياً.

مثال بالكود (Pseudo-code بسيط باستخدام Node.js و RabbitMQ)

هذا مثال مبسط جداً ليوضح الفكرة. سنستخدم مكتبة amqplib.

منتج الحدث (في خدمة الطلبات)


// order-service.js
const amqp = require('amqplib/callback_api');

function placeOrder(orderDetails) {
    // 1. Save order to database with 'PENDING' status
    console.log('Order saved to DB.');

    // 2. Publish 'OrderPlaced' event
    amqp.connect('amqp://localhost', function(error0, connection) {
        if (error0) throw error0;
        connection.createChannel(function(error1, channel) {
            if (error1) throw error1;

            const exchange = 'orders_exchange';
            const msg = JSON.stringify(orderDetails);

            channel.assertExchange(exchange, 'fanout', { durable: false });
            channel.publish(exchange, '', Buffer.from(msg)); // '' means publish to all queues bound to this exchange
            console.log(" [x] Sent %s", msg);
        });

        setTimeout(function() {
            connection.close();
        }, 500);
    });

    // 3. Immediately return response to user
    return { success: true, message: 'Your order has been received!' };
}

// Example usage
placeOrder({ orderId: 123, userId: 456, amount: 99.99 });

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


// notification-service.js
const amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(error0, connection) {
    if (error0) throw error0;
    connection.createChannel(function(error1, channel) {
        if (error1) throw error1;

        const exchange = 'orders_exchange';

        channel.assertExchange(exchange, 'fanout', { durable: false });

        // Create a temporary, exclusive queue
        channel.assertQueue('', { exclusive: true }, function(error2, q) {
            if (error2) throw error2;

            console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", q.queue);
            channel.bindQueue(q.queue, exchange, '');

            channel.consume(q.queue, function(msg) {
                if(msg.content) {
                    const orderDetails = JSON.parse(msg.content.toString());
                    console.log(" [.] Received order details: ", orderDetails);
                    // Logic to send email/SMS
                    console.log(` ---> Sending notification for order ${orderDetails.orderId}...`);
                }
            }, {
                noAck: true // Auto-acknowledgement
            });
        });
    });
});

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

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

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

نصيحة أبو عمر

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

أبو عمر

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

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

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

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

آخر المدونات

أدوات وانتاجية

طرفيتي كانت بئرًا بلا قرار: كيف أنقذتني أدوات مثل ‘fzf’ و ‘zsh’ من جحيم البحث عن الإبرة في كومة قش؟

أشارككم تجربتي كـ "أبو عمر"، مبرمج فلسطيني، في تحويل الطرفية (Terminal) من كابوس مربك إلى أداة إنتاجية خارقة. اكتشفوا كيف أنقذتني أدوات مثل zsh و...

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

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

هل شعرت يومًا أنك تبني تطبيقات رائعة... لكن لا أحد يستخدم ميزاتها؟ هذا ما حدث معي بالضبط. في هذه المقالة، أشارككم كيف أنقذتني أداة بسيطة...

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

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

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

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

خوادمي كانت تعمل 24/7: كيف أنقذتني “الحوسبة بدون خوادم” (Serverless) من جحيم الفواتير؟

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

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

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

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

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