المستودع الموحد مقابل المستودعات المنفصلة
المستودع الموحد مقابل المستودعات المنفصلة
طريقة تنظيم الكود عبر المستودعات تشكّل ثقافة الهندسة بأكملها — مدى سرعة شحن الفرق للمنتج، ومدى تباعد الاعتماديات، ومدى صعوبة التغييرات التي تمسّ مكونات متعددة في آنٍ واحد. يحلّل هذا الدرس قرار المستودع الموحد مقابل المستودعات المنفصلة على مستوى الإنتاج الفعلي، ويفحص الأدوات الحقيقية التي تجعل كل نهج قابلاً للتطبيق، ويشرح ما تفعله Google وMeta وMicrosoft وAirbnb فعلياً والسبب وراء ذلك.
التعريفات الأساسية
المستودع الموحد (Monorepo) يخزّن مشاريع متعددة — خدمات، ومكتبات، وكود بنية تحتية، وأدوات — في مستودع Git واحد تحت رسم بياني موحّد للإصدارات. أما المستودعات المنفصلة (Polyrepo) فتمنح كل مشروع مستودعه الخاص بفروعه وخطوط CI وإيقاع إصداراته المستقل. يوجد نمط ثالث هجين يُعرف بـMeta-repo يستخدم مستودعًا رئيسيًا رفيعًا يجمع المستودعات المنفصلة عبر Git submodules أو أدوات كـmeta — لكنه يرث أسوأ ما في النهجين ونادرًا ما يكون الخيار الأمثل.
لماذا اختارت الشركات العملاقة المستودع الموحد
تدير Google أكبر مستودع موحد في العالم: مستودع داخلي واحد يُعرف بـ"google3" يحتوي على أكثر من 86 تيرابايت من البيانات و2 مليار سطر من الكود، ويخدم عشرات الآلاف من المهندسين. مستودع Meta المُوحّد "fbsource" بحجم مماثل. بنت كلتا الشركتين أدوات مخصصة (Bazel وBuck على التوالي) لأن أي نظام VCS جاهز لم يكن قادرًا على التوسع بهذا الحجم. مبرراتهم الأساسية:
- التغييرات العرضية الذرّية — أعد تسمية واجهة برمجية، وحدّث كل من يستخدمها في إيداع واحد. لا حاجة لطبقات توافق للإصدارات ولا تنسيق "تغيير كاسر" بين المستودعات.
- مصدر حقيقة واحد لإصدارات الاعتماديات — لا مشكلة الاعتمادية الماسية (diamond dependency) بين الفرق.
- استثمار الأدوات المشتركة موزّع على الجميع — إعداد lint واحد، قالب CI واحد، سياسة ماسح أمني واحدة.
- إعادة استخدام الكود واكتشافه أسهل — يمكن للمهندسين قراءة أي مكتبة والمساهمة فيها دون التبديل بين المستودعات.
أين تتفوق المستودعات المنفصلة
ليست كل مؤسسة بحجم Google. المستودعات المنفصلة هي الخيار الافتراضي الصحيح عندما:
- استقلالية الفريق أهم من التنسيق — قطارات إصدار مستقلة، مكدسات تقنية مختلفة، مناوبات استجابة منفصلة.
- المساهمون الخارجيون أو المصدر المفتوح — منح مورّد وصولاً لمستودع خدمة واحدة دون كشف كل شيء آخر.
- حدود الامتثال التنظيمي — كود نطاق PCI، وخدمات معالجة بيانات HIPAA، أو بنية تحتية تحتوي على أسرار يمكن عزلها بضوابط وصول أضيق.
- الحجم في المراحل المبكرة — قبل أن يكون لديك استثمار أدواتي يجعل المستودع الموحد سريعًا، تتميز المستودعات المنفصلة بأقل احتكاك تشغيلي.
المشكلة الحقيقية للمستودع الموحد: Git لا يتوسع
صُمِّم Git لتطوير نواة Linux — مشروع واحد ومئات المساهمين. على مستوى المستودع الموحد، يستغرق git status دقائق على شجرة بـ10 ملايين ملف، وgit clone أمر غير عملي، وgit log --all يصبح ضوضاء. آليتان تعالجان هذا مباشرة.
الفحص المتفرق في وضع المخروط (Sparse Checkout - Cone Mode)
يتيح الفحص المتفرق للمطوّر سحب المجلدات التي يحتاجها فقط. اعتبارًا من Git 2.25، وضع المخروط (cone mode) هو النهج الموصى به في بيئات الإنتاج — يقيّد المسارات إلى مجموعة من المجلدات باستخدام مطابقة نمط بسيطة أسرع بمراتب من النهج القديم القائم على wildcards.
--filter=blob:none (الاستنساخ الجزئي — يحذف blobs الملفات حتى الحاجة) مع --depth=1 (الاستنساخ الضحل) للحصول على أسرع عملية استنساخ ممكنة. أفاد مهندسون في Shopify وTwitter بتقليص وقت الاستنساخ من 45 دقيقة إلى أقل من دقيقتين على مستودعات موحدة ضخمة باستخدام هذين الخيارين معاً.
العزل على مستوى نظام البناء: Bazel وNx
المشكلة الأعمق في المستودع الموحد ليست Git — بل CI. إذا كان كل إيداع يؤدي لإعادة بناء كاملة، فإن مستودعاً موحداً بـ500 حزمة يصبح غير قابل للاستخدام. الحل هو نظام بناء يفهم رسم الاعتمادية ويعيد البناء والاختبار للحزم المتأثرة فقط.
Bazel (النسخة مفتوحة المصدر من Blaze الداخلي لـGoogle) يُصنّف كل هدف بناء بشكل صريح. ملف BUILD يعلن المدخلات والمخرجات والاعتماديات. Bazel يُجزّئ المدخلات ويخزّن المخرجات عن بُعد — إذا لم يتغير شيء في المنبع، تُقدَّم نتيجة الاختبار من الكاش في ميلي ثانية.
لمستودعات JavaScript/TypeScript الموحدة، تؤدي Nx وTurborepo الدور ذاته. Nx يبني رسم المشروع من اعتماديات package.json ومسارات tsconfig؛ Turborepo يستخدم pipeline معرّفاً في turbo.json. كلاهما يدعم الكاش عن بُعد عبر خلفيات مستضافة (Nx Cloud، Vercel).
ما تفعله العمالقة فعلياً
- Google — مستودع موحد (google3)، Bazel، Piper VCS (خاص)، Critique لمراجعة الكود. تقريباً كل الكود في مستودع واحد.
- Meta — مستودع موحد (fbsource)، Sapling VCS (مفتوح المصدر 2022)، Buck2. اختاروا Sapling بدلاً من Git لأن نموذج كائنات Git لا يتعامل مع هذا الحجم.
- Microsoft — مستودع Windows يستخدم GVFS (Git Virtual File System) لافتراضية نظام الملفات بحيث يُنزّل Git الملفات عند الوصول فقط. فتحوا المصدر كبديل عملي لـsparse checkout الكامل.
- Airbnb / Lyft — مستودعات منفصلة مُنظَّمة حسب فريق الـdomain؛ يستثمرون بكثافة في أدوات المنصة للحفاظ على تزامن إصدارات الاعتماديات عبر المستودعات.
- Shopify — هاجروا من monolith Rails إلى مستودع موحد قائم على المكوّنات باستخدام
packwerkلتطبيق الحدود دون تقسيم المستودعات.
git filter-repo أو git subtree) لدمج التاريخيات وإعادة بناء pipelines CI من الصفر. اتخذ القرار المعماري مبكراً، قبل أن يكون لديك 40 مستودعاً و200 مهندس.
إطار اتخاذ القرار
استخدم هذا النموذج الذهني عند الاختيار:
- كم مرة تشترك خدماتك في الكود؟ كلما زاد الكود المشترك والتغييرات العرضية، كلما زادت مزايا المستودع الموحد.
- هل لديك فريق منصة لبناء أدوات المستودع الموحد؟ بدون Bazel/Nx/Turborepo وكاش عن بُعد، يتحول المستودع الموحد إلى عنق زجاجة CI في غضون أشهر.
- هل التحكم في الوصول أو حدود الامتثال متطلب صارم؟ إذا نعم، فالمستودعات المنفصلة هي الطريق الأسهل.
- ما حجمك الحالي؟ تحت ~10 خدمات و~20 مهندساً، كلا النهجين يعمل؛ حسّن تجربة المطوّر لا نقاء المعمارية.
platform-monorepo، data-monorepo). يجني معظم فائدة الأدوات المشتركة مع إبقاء التحكم في الوصول قابلاً للإدارة وأداء Git معقولاً دون أدوات VCS متخصصة.
أنماط الهجرة العملية
إذا كنت بحاجة لدمج مستودع منفصل موجود في مستودع موحد، يُعدّ git subtree النهج المعياري للحفاظ على التاريخ:
المستودعات الموحدة والمنفصلة ليست مواقف أخلاقية — بل هي مقايضات هندسية. الإجابة الصحيحة تعتمد على حجم فريقك، ونضج أدواتك، ومتطلبات الامتثال، ومدى تشارك خدماتك للكود فعلياً. الأهم أن يكون الاختيار متعمداً وأن تستثمر في الأدوات التي تجعل النهج المختار سريعاً وقابلاً للصيانة على نطاق واسع.