تخيّل نقطة نهاية للدفع (checkout) تؤدّي — على الورق — مهمّة واحدة: تحصيل المبلغ. أمّا في الواقع فهي تستدعي المخزون، ثمّ فحص الاحتيال، ثمّ سجلّ نقاط الولاء، ثمّ البريد الإلكتروني، ثمّ خطّ التحليلات — خمس قفزات HTTP متزامنة، تحبس كلٌّ منها مؤشّر التحميل أمام العميل. في يوم ثلاثاء هادئ يعود ذلك كلّه خلال 300 مللي ثانية. لكن أثناء عرض تخفيضات خاطف تبدأ خدمة الولاء بالتلكّؤ، وفجأة لا يقدر أحد على الشراء. البطاقة خُصمت. والطلب «فشل».
هذا ليس خللاً تُصلحه برقعة، بل هو المعمارية تخبرك أنها لم تُبنَ أصلاً لتعمل بهذه الطريقة.
التكلفة الحقيقية لنمط الطلب-الاستجابة
تبدو النداءات المتزامنة بسيطة لأنها تحاكي طريقة تفكيرنا: تطرح سؤالاً وتنتظر الجواب. أمّا التكلفة الخفيّة فهي الاقتران الزمني (temporal coupling) — إذ يجب أن تكون كلّ خدمة في السلسلة سليمة وسريعة ومتاحة في اللحظة نفسها بالضبط، وإلا تحمّل المُستدعي وزر الفشل.
والحساب لا يرحم. اربط خمس خدمات يحقّق كلٌّ منها زمن تشغيل محترمًا بنسبة 99.9%، وستهبط الإتاحة المجمّعة إلى نحو 99.5% — أي ما يقارب 3.6 ساعة توقّف شهريًا لم يتسبّب بها أيّ فريق بمفرده. ويتراكم زمن الاستجابة على النحو نفسه: تتجمّع قيم الـ p99، فتصبح نقطة نهايتك «السريعة» بسرعة أبطأ خدمة لديها في أسوأ أيامها. لقد حوّلت خمسة أنظمة مستقلّة إلى كائن واحد هشّ يفشل مجتمعًا.
ما الذي تعنيه «المعمارية المدفوعة بالأحداث» فعلاً
الوهم الشائع أن المعمارية المدفوعة بالأحداث تعني «ضع طابورًا (queue) في المقدّمة». لكن الطابور مجرّد سباكة؛ التحوّل الحقيقي في ما تُرسله عبره.
في نمط الطلب-الاستجابة تُصدر أوامر (commands): «اخصم من هذه البطاقة»، «أنقِص هذا المخزون». المُستدعي يملك النتيجة وينتظرها. أمّا في النظام المدفوع بالأحداث فتنشر حقائق (facts): OrderPlaced، PaymentCaptured. يذكر المُنتِج ما حدث ثمّ يمضي في شأنه، لا يعرف من يستمع ولا يعنيه ذلك. فخدمة الولاء وخدمة البريد وخطّ التحليلات، يشترك كلٌّ منها ويتفاعل وفق وتيرته الخاصة.
الأوامر تقرن المُرسِل بالمستقبِل، أمّا الأحداث فتقلب المعادلة: المستقبِل يعتمد على المُرسِل، والمُرسِل لا يعتمد على أحد.
هذا القلب هو جوهر اللعبة كلّها. مستهلك جديد الربع القادم؟ يشترك في الحدث نفسه. لا تغيير على المُنتِج، ولا قفزة متزامنة جديدة، ولا طريقة إضافية ينهار بها الدفع.
متى يتفوّق النمط اللامتزامن
الجأ إلى الأحداث حين تلمح هذه الأنماط:
- التوزيع المتفرّع (Fan-out). يقع حدث واحد وتحتاج خمسة أنظمة غير مترابطة إلى معرفته. التوزيع المتزامن خمس طرق للفشل؛ أمّا الحدث المنشور فطريق واحد.
- الحِمل المتذبذب غير المنتظم. الطابور يمتصّ الصدمات. فهو يتيح لدفقة من 10,000 طلب أن تنساب بهدوء إلى مستهلك يعالج 500 طلب في الثانية بأريحية، بدلاً من أن تُطيح به.
- المهامّ طويلة التنفيذ. ترميز الفيديو، وتوليد التقارير، وفحوص KYC. لا ينبغي لأحد أن يُبقي اتصال HTTP مفتوحًا تسعين ثانية.
- فكّ الارتباط بين الفرق. حين تتطلّب «إضافة ميزة» اجتماعًا مع ثلاثة فرق أخرى، تتيح الأحداث لتلك الفرق أن تبني اعتمادًا على حقائقك دون حجز موعد في تقويمك.
لا شيء من هذا مجّاني. أنت تقايض الاتّساق الفوري باتّساق نهائي (eventual consistency)، ولهذه المقايضة أنياب. فالمستهلكون يجب أن يكونوا خاملي التكرار (idempotent) — إذ سيُسلَّم الحدث نفسه مرّتين، وعليك أن تتجاوز ذلك بلا أثر. ويكفّ التنقيح عن كونه أثر تتبّع (stack trace) ليصير خطًّا زمنيًا موزّعًا. وتصبح ضمانات الترتيب دقيقة وشائكة. وإن كان فريقك عاجزًا حتى الآن عن الإجابة عن سؤال: «ماذا يحدث حين يصل هذا الحدث مرّتين، خارج الترتيب، متأخّرًا ساعة؟» فإنّ اللامتزامن سيؤذيك قبل أن ينفعك.
متى يبقى نمط الطلب-الاستجابة هو الخيار الصحيح
اللامتزامن أداة لا عقيدة. أبقِ الأمر متزامنًا حين يقف المستخدم منتظرًا جوابًا محدّدًا — تسجيل دخول، أو استعلام عن رصيد، أو نتيجة بحث. أبقِه متزامنًا في تدفّقات «اقرأ ما كتبت» (read-your-own-writes)، حيث يتوقّع المستخدم أن يرى السجلّ الذي حفظه للتوّ. وأبقِه متزامنًا في عمليات CRUD البسيطة حقًّا، حيث لا يضيف ناقل الأحداث سوى عبء تشغيلي دون أيّ مقابل. وأكثر خطأ أراه ليس نقصًا في التصميم المدفوع بالأحداث، بل فرقٌ تدفع أمر «المستخدم ضغط حفظ» عبر Kafka وتسمّي ذلك معمارية.
من أين تبدأ صباح الاثنين
لا تُعِد كتابة النظام. ابحث عن توزيع متفرّع متزامن واحد مؤلم — غالبًا طلب أو تسجيل يُطلق سلسلة من الآثار الجانبية — وأطلِق المستهلكين غير الجوهريين إلى حدث. نفّذ الدفع بشكل متزامن، وانشُر OrderPlaced ودَع البريد والولاء والتحليلات تتفاعل لا متزامنًا. واستعن بنمط الصندوق الصادر (outbox pattern) كي لا تفقد حدثًا حين ينجح التزام قاعدة البيانات ويفشل نداء الوسيط. فوصلٌ واحد مُحكَم الاختيار سيعلّم فريقك عن اللامتزامن أكثر من أيّ مخطّط.
والسؤال الجدير بالطرح قبل نقطة نهايتك القادمة: هل يحتاج المُستدعي هذا الجواب الآن حقًّا، أم أنّنا ظللنا نُبقيه ينتظر بحكم العادة فحسب؟