قضيتُ أربع سنوات في استخراج خدمات من monolith والأربع التالية في دمجها بهدوء من جديد. الـmodular monolith ليس هزيمة - إنه خيار تصميم يُبقي المرونة مفتوحة وقصة النشر مملّة. هذا المنشور هو الحجّة لاختياره عن قصد، مع الشواهد من الترحيل الذي علّمني الدرس بالطريقة الصعبة.
حقبة الاستخراج
من 2014 إلى 2018، عملتُ على منصّات مستشفيات (AFAQ، HAKEEM)، منصّات توظيف
(Talentera)، وعدد آخر حيث كانت المحادثة تنجرف دائماً بنفس الطريقة: “يجب
أن نقسّم هذا إلى خدمات.” الخدمات كانت كيف تُثبت أن codebase حديث (Modern).
Kubernetes كان كيف تُثبت أن فريق البنية التحتية حديث. عشرات مستودعات بملفات
pyproject.toml كانت كيف تُثبت أن ممارستك الهندسية حديثة.
فعلنا ذلك. معظم تلك الاستخراجات شُحنت. بعضها حتى نجح.
هذا الجزء الذي لا يقوله أحد بصوت عالٍ: ما جعل معظم تلك الخدمات “تنجح” هو أنها بقيت مرتبطة بإحكام بقاعدة البيانات الأصلية. حدود الخدمات كانت أوهاماً. حدود المعاملات لم تكن. حين كنا نحتاج لتنسيق تغييرات عبر الخدمات الجديدة - وهو ما حدث أسبوعياً تقريباً، لأن حدود نطاقنا لم تتطابق مع حدود خدماتنا -فعلنا ذلك بمهام cron وتنسيق Slack وأمل.
حقبة إعادة الدمج
من 2020 في Bytro فصاعداً، انعكس النمط. بدأنا بنظام legacy event-driven كان مقسّماً إلى خدمات تبدو معقولة وأعدنا سحبها بهدوء في كيان قابل للنشر واحد أسمّيناه، حسب الجمهور:
- “Modular monolith” في وثائق التصميم.
- “الشيء الكبير” في اجتماعات الهندسة.
- “معمارية معقولة لا أحد يكتب عنها في المؤتمرات” في خاصّ.
هذا لم يكن تراجعاً. الأشياء التي استعدناها:
- نشر واحد. CI pipeline واحد. Rollback واحد. مشكلة version-skew واحدة للتفكير فيها أثناء الحادثة.
- حدود قابلة لإعادة الهيكلة. تريد نقل دالة من الموديول A إلى B؟ إنها إجراء IDE، لا RFC.
- معاملات صادقة. إن كانت عمليتان تحتاجان أن تكونا ذرّيتين، كان يمكنهما ذلك. إن لم تكونا، ما زلنا نفصل على حدود الموديول - لكننا لم نكذب على ضمان الذرّية لنربح قصة نشر.
- تطوير محلي أسرع.
docker compose upيفتح عملية واحدة، لا تسعة. - مراقبة أرخص. traces خدمة واحدة هي شجرة. traces تسع خدمات هي رسم بياني
- والرسم البياني يكذب على نصف الحواف لأن نصف spans-ك لم تُحوّل trace context بشكل صحيح.
ما لا يُقال عن microservices
سبب شحن microservices تنظيمي، لا تقني. تتيح للفِرق النشر باستقلالية. هذا هو المقترح كاملاً، وهو جيّد - إن عندك أكثر من فريق واحد دورة نشره في تعارض فعلي.
إن عندك ثلاثة فِرق تنشر إلى نفس بيئة staging في نفس الأيام بنفس الترتيب، ليس عندك مشكلة اقتران نشر. عندك ثلاثة فِرق وmonolith واحد، وهذا ترتيب ناجح.
الأمراض التي شاهدتُها في ثلاث شركات حتى الآن هي هذه:
- فريق من ثمانية أشخاص يبني monolith.
- يبدؤون في الشعور باحتكاك.
- أحدهم يقرأ منشوراً عن كيفية عمل Netflix بـmicroservices.
- يقضون 18 شهراً في استخراج خدمات.
- في النهاية، عندهم ثمانية أشخاص و14 خدمة.
- كل خدمة لا يملكها أحد بشكل خاص.
- كل تغيير ما زال يتطلّب تنسيقاً - لكن الآن عبر حدود الخدمة، مع eventual consistency، مع distributed tracing، مع نمط فشل جديد يُسمّى “الخدمة X معطّلة لكن الخدمة Y تعمل وماذا يجب أن تفعل الخدمة Z؟”
الاحتكاك الذي كانوا يشعرون به في الخطوة 2 لم يكن مشكلة حدود خدمة. كانت مشكلة حدود موديول داخلي. يمكن إصلاح تلك دون إدخال شبكة.
ما تعنيه “modular” فعلاً
“modular” في “modular monolith” تُنجز عملاً. الانضباط يبدو هكذا:
- حدود الموديول مُفرَضة بنظام البناء، لا بالتقاليد. إن كان
ordersيستطيع الاستيراد منbillingوقت التجميع، ليس عندك حدود. عندك أمنية. استخدم ما تدعمه لغتك: Internal packages في Go، Crates في Rust، Project references في TypeScript، نظام الموديول في Java. اختر واحداً، أفرضه، اكسر البناء على الانتهاكات. - حدود الموديول تعكس حدود النطاق. هذا الجزء الصعب. لن تُصيبها في المرة الأولى. تُعيد هيكلتها مرتين، ثم يظهر الشكل الصحيح. حقيقة أن إعادة هيكلتها رخيصة داخل monolith هي بالضبط سبب كونه المكان الصحيح لإجراء إعادة الهيكلة.
- تعهّد بما يعبر الحدود. قيم، لا مراجع. Events، لا استدعاءات طرق. أنواع منشورة، لا أنواع داخلية. حين تكون تلك في مكانها، استخراج موديول لاحقاً كخدمة هو عمل عطلة نهاية أسبوع. بدونها، الاستخراج مشروع 18 شهراً ينتج monolith موزّعاً.
متى تستخرج فعلاً خدمة
لستُ ضدّ الخدمات. استخرجتُ عشرات. الإشارات التي أبحث عنها الآن قبل القيام بذلك كلها تنظيمية أو تشغيلية - لا تقنية:
- فريق محدّد يريد النشر بدورة مختلفة، الآن، لا نظرياً بعد عام.
- حمل عمل محدّد له ملف runtime أو scaling لا يستطيع الـmonolith خدمته بسعر معقول - مثل: مسار استدلال ML جائع للمعالج.
- قدرة محدّدة لها حدود امتثال أو استئجار مختلفة - مثل: سطح غني بـPII يحتاج مسار تدقيق خاص به.
- تكامل مورّد هو خدمة طبيعياً - مثل: مستقبل webhook يجب أن يستمر في التشغيل أثناء نشر monolith.
القاسم المشترك: كل واحدة منها ضغط حقيقي، مُسمّى، ملموس. “لكي نكون حديثين” ليست على هذه القائمة ولن تكون أبداً.
الشواهد
هذا هو الجزء الذي كنت فيه عادةً أستشهد برقم p99 محدّد أو تحسين وقت نشر مُحسَّب. بدلاً من ذلك سأستشهد بالشيء الذي أقنعني أكثر:
في Bytro، بعد توحيد topology خدمة مجزّأة سابقاً في monolith موديولي محكم، هدأت دورة استعداد الفريق. ليس لأن الحوادث قلّت - كان عددها تقريباً نفسه. لكن حادثة “تلقّت خدمة واحدة pager” هي مشكلة مهندس واحد ومحطة طرفية واحدة وrollback واحد. حادثة “ست خدمات مرتبكة في حالة بعضها” هي مشكلة مؤتمر اتصال ستة مهندسين، ولا تستعيد النوم الذي خسرته لأجلها.
دورة الاستعداد هي كاشف الحقيقة لمعماريتك. إن كانت دورتك صحية، المعمارية تعمل. إن كانت دورتك ضريبة شعبية، المعمارية خاطئة - بغضّ النظر عن الشكل الذي تُرسَم به على اللوح.
الجزء الحديث
سأعترف بالهادئ: في 2025 و2026، الـmonolith + مجموعة التقنيات المملّة هي التي سأشحن عليها الأشياء الأكثر إثارة. الهندسة بمساعدة الذكاء الاصطناعي أسهل بكثير حين يتّسع codebase كاملاً في نافذة سياق الوكيل. الـmodular monolith يتّسع. تسع خدمات بأربع لغات مختلفة وستة عشر صيغة إعداد مختلفة لا تتّسع.
إن أردتَ أن تكون أدوات الذكاء الاصطناعي الخاصة بك مفيدة، أعطها codebase يمكنها قراءته. الحجّة لصالح الـmonolith في 2026 هي، بشكل غير متوقّع، أنه المعمارية الأسرع لسير عمل هندسي يقوده LLM. وهذه، بطريقة ما، أكثر جملة أكتبها بمذاق 2026 في حياتي.