حكاية “الزر” الذي كاد أن يدمّر المشروع
خليني أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة. كنا شغالين على منتج كبير، عبارة عن منصة تحليل بيانات معقدة. المنتج كان مقسم لثلاثة أجزاء رئيسية: واجهة المستخدم (Frontend) مكتوبة بـ React، الواجهة الخلفية (Backend) بـ Node.js، ومكتبة مشتركة (Shared Library) فيها كل منطق العمل الأساسي والـ Types تبعت TypeScript اللي بنستخدمها بين المشروعين.
طبعًا، كأي فريق “منظم” في ذلك الوقت، كان كل جزء في مستودع Git خاص فيه. يعني عنا 3 مستودعات (Repositories). الأمور كانت ماشية، مش تمامًا بس ماشية. لحد ما إجا يوم طلب فيه مدير المنتج تغيير بسيط على زر معين في واجهة المستخدم. تغيير في لونه وشوية سلوك عند الضغط عليه.
المبرمج المسؤول عن الواجهة الأمامية فتح المستودع تبعه، عدّل على مكتبة المكونات المشتركة (اللي كانت برضه الها مستودع رابع!)، عمل إصدار جديد للمكتبة، وبعدين رجع لمستودع الواجهة الأمامية وحدّث الإصدار، وغير الكود عشان يستخدم النسخة الجديدة. الشغل أخذ يوم كامل بين تحديث ونشر واعتماد. ولما رفع التغييرات، كل شيء كان شغال مية بالمية على جهازه.
بس لما عملنا دمج ونشر (Deploy) للإنتاج، صارت الكارثة. جزء كامل من لوحة التحكم توقف عن العمل. البيانات ما عادت تظهر. قضينا ساعات طويلة، والله يا جماعة كانت ليلة صعبة، بنحاول نعرف شو المشكلة. كل شيء يبدو سليمًا، الكود صحيح، والاختبارات على كل مستودع منفصل ناجحة!
بعد تحقيق طويل وتدقيق سطر بسطر، اكتشفنا المصيبة: الواجهة الخلفية (Backend) كانت بتستخدم نسخة أقدم من المكتبة المشتركة (Shared Library). التغيير اللي صار في الزر، كان معتمد على تغيير آخر في طريقة معالجة البيانات في المكتبة المشتركة، والـ Backend ما كان عنده علم بهذا التغيير. صار عنا عدم توافق صامت بين الأنظمة. كنا مثل جزر معزولة، كل جزيرة الها قانونها الخاص، ولما حاولنا نبني جسر بينهم، انهار كل شيء.
في تلك الليلة، ونحن منهكون، نظر أحد كبار المبرمجين في الفريق إلينا وقال: “يا جماعة، لازم نلاقي حل لهالمجزرة. شغلنا ما بينفع يضل هيك مفرّق”. ومن هنا بدأت رحلتنا مع ما يُعرف بـ “المستودعات الأحادية” أو الـ Monorepos.
ما هي المستودعات الأحادية (Monorepos)؟
ببساطة شديدة، الـ Monorepo هو عكس اللي كنا بنعمله. بدل ما يكون عندك مستودع Git لكل مشروع أو مكتبة، بتحط كل المشاريع والمكتبات المتعلقة ببعضها في مستودع واحد عملاق.
تخيلها هيك: بدل ما يكون عندك شنطة عدة للكهربا، وشنطة للسباكة، وصندوق للمسامير، وكل وحدة في غرفة، الـ Monorepo هو كراج كبير منظم، فيه كل عددك وأدواتك مقسمة على رفوف وخزائن واضحة. كل شيء في مكان واحد، سهل الوصول إليه، وسهل تعرف شو عندك وشو ما عندك.
باختصار: الـ Monorepo هو مستودع واحد يحتوي على عدة مشاريع (packages/apps) مستقلة لكنها مترابطة.
الفرق بين الـ Monorepo والـ Polyrepo (تعدد المستودعات)
- Polyrepo (الوضع التقليدي): كل مشروع في مستودع خاص. هذا هو النهج الذي تتبعه معظم المشاريع الصغيرة.
- Monorepo (النهج الأحادي): كل المشاريع في مستودع واحد. هذا النهج تتبعه شركات عملاقة مثل Google, Meta, و Microsoft.
لماذا كانت مشاريعنا “جزرًا معزولة”؟ – مشكلة الـ Polyrepo
قصتنا مع “الزر” بتلخص معظم مشاكل تعدد المستودعات (Polyrepo)، بس خلينا نفصّلها بشكل أوضح:
1. جحيم الاعتماديات (Dependency Hell)
هذه كانت أكبر مشكلة عنا. الواجهة الأمامية تستخدم `lodash` نسخة 4.17، والواجهة الخلفية تستخدم نسخة 4.15، والمكتبة المشتركة نسخة ثالثة. لما تحدث مشكلة، بتبدأ تسأل حالك: “هل المشكلة من الكود تبعي، ولا من اختلاف نسخ المكتبات؟”. هذا التضارب بيخلق أخطاء غامضة وصعبة التصحيح.
2. صعوبة إعادة استخدام الكود
كان عنا ملف فيه دوال (functions) مساعدة للتعامل مع التواريخ. الفريق الأمامي عمل نسخة منه، وفريق الخلفية عمل نسخة ثانية. لما اكتشفنا خطأ في دالة منهم، اضطرينا نصلحه في مكانين! عشان نتجنب هذا، كنا بنعمل مكتبة خاصة وننشرها على NPM. بس هاي العملية بحد ذاتها كانت صداع: اعمل إصدار، انشر، روح على كل مشروع حدث النسخة… عملية بطيئة ومملة وبتفتح مجال للنسيان والخطأ.
3. التغييرات الشاملة (Atomic Changes)
لما تحتاج تعدل على ميزة بتمس الواجهة الأمامية والخلفية والمكتبة المشتركة، أنت بحاجة لعمل 3 Pull Requests مختلفة على 3 مستودعات. لازم تنسق بينهم، وتتأكد إنهم يندمجوا بالترتيب الصحيح. هذا التعقيد بيزيد من احتمالية نشر نسخة غير مكتملة أو غير متوافقة من النظام.
4. تعقيد إعداد بيئة التطوير
لما يجي مطور جديد على الفريق، بنعطيه قائمة طويلة: “اعمل `git clone` للمستودع الأول، والثاني، والثالث… بعدين روح على كل واحد واعمل `npm install`…”. العملية بتاخذ وقت طويل ومحبطة، وكثير من الأحيان بتصير مشاكل في الإعدادات.
كيف أنقذتنا الـ Monorepos؟ – فوائد عملية
بعد ليلة “الزر” المشؤومة، قررنا نتبنى الـ Monorepo. الموضوع أخذ شوية وقت وجهد في البداية، لكن الفوائد كانت تستحق كل دقيقة.
1. إدارة مركزية للاعتماديات
صار عنا ملف `package.json` واحد في جذر المشروع. كل المشاريع الداخلية بتستخدم نفس نسخة الاعتماديات. انتهى كابوس “أي نسخة من المكتبة بنستخدم؟”. لو بدنا نحدّث مكتبة، بنحدّثها في مكان واحد، والتغيير بيطبق على الكل. هذا لوحده حل 80% من مشاكلنا.
2. سهولة خيالية في مشاركة الكود
بدل نشر مكتبات على NPM، صرنا نعمل “حزم” (packages) داخلية. الهيكل صار يشبه هيك:
/monorepo-project
├── apps
│ ├── web-app # تطبيق الويب (React)
│ └── backend-api # الواجهة الخلفية (Node.js)
├── packages
│ ├── ui-components # مكتبة مكونات الواجهة المشتركة
│ ├── shared-utils # الدوال المشتركة
│ └── ts-config # إعدادات TypeScript المشتركة
├── package.json
└── pnpm-workspace.yaml # أو yarn أو npm
الآن، من داخل `web-app`، بقدر أستدعي مكون من `ui-components` بكل بساطة:
import { Button } from 'ui-components';
كأنه مكتبة من NPM، لكنه موجود محليًا! أي تغيير بعمله في `ui-components` بيظهر فورًا في `web-app` بدون نشر أو تحديث إصدارات. هذا سرّع عملية التطوير بشكل لا يصدق.
3. تغييرات ذرّية (Atomic Commits)
الآن، لما أعدل على ميزة بتمس 3 مشاريع، بعمل commit واحد و Pull Request واحد بيشمل كل التغييرات. المراجع (Reviewer) بيشوف القصة كاملة في مكان واحد. ولما ندمج، بنكون متأكدين 100% إنه كل أجزاء النظام متوافقة مع بعضها.
أدوات للمستودعات الأحادية: صديقنا الجديد Turborepo
لما كبر الـ Monorepo تبعنا، بدأت تظهر مشكلة جديدة: البطء. لما تعمل `build` أو `test`، النظام كان يحاول يعيد بناء كل شيء في كل مرة، حتى لو ما تغير. هنا تعرفنا على أدوات إدارة الـ Monorepo، وعلى رأسها Turborepo.
Turborepo هو نظام بناء (Build System) ذكي مصمم خصيصًا للـ Monorepos. فكرته بسيطة وعبقرية.
أهم ميزات Turborepo
- التخزين المؤقت الذكي (Intelligent Caching): هاي هي الميزة القاتلة. Turborepo بيعمل “بصمة” للكود تبعك. لو شغّلت أمر `build`، بيخزن النتيجة في كاش (cache). المرة الجاية لو شغّلت نفس الأمر والكود ما تغير، ما بعيد البناء! بيسحب النتيجة من الكاش في أجزاء من الثانية. هذا بيوفر علينا دقائق، وأحيانًا ساعات، من وقت الانتظار كل يوم.
- تنفيذ المهام المتوازي (Parallel Execution): Turborepo بيفهم اعتماديات مشاريعك، وبيقدر يشغل المهام اللي ما بتعتمد على بعضها بشكل متوازي، وهذا بيزيد السرعة بشكل كبير.
- تحديد نطاق المهام (Task Scoping): بتقدر تقله يشغل أمر معين فقط على المشاريع اللي تأثرت بآخر تغييرات عملتها. مفيد جدًا في الـ CI/CD.
مثال عملي مع Turborepo
عشان تستخدم Turborepo، كل اللي بتحتاجه هو ملف `turbo.json` في جذر مشروعك. شكله بسيط جدًا:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"lint": {},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
شرح سريع:
pipeline: هنا بتعرّف الأوامر تبعتك (`build`, `lint`, `test`)."dependsOn": ["^build"]: هاي أهم نقطة. الإشارة `^` معناها “قبل ما تعمل `build` للمشروع الحالي، تأكد إنك عملت `build` لكل اعتمادياته الداخلية”. يعني ما رح يبني `web-app` قبل ما يتأكد إنه `ui-components` مبنية وجاهزة."outputs": ["dist/**"]: هنا بتخبر Turborepo وين يلاقي نتائج عملية البناء عشان يخزنها في الكاش.
الآن، بدل ما تشغل أوامر معقدة، كل ما عليك هو كتابة أمر واحد في جذر المشروع: `turbo run build`، وهو سيتكفل بالباقي بأسرع طريقة ممكنة.
نصائح من خبرة أبو عمر
بعد سنوات من استخدام الـ Monorepos، هاي شوية نصائح من القلب:
- ابدأ ببساطة: مش ضروري تبدأ بـ Turborepo من أول يوم. ابدأ باستخدام مدير الحزم المفضل عندك (pnpm, yarn, npm) مع خاصية الـ workspaces. لما تحس إن المشروع صار بطيء، وقتها ضيف Turborepo.
- نظّم مشاريعك جيدًا: اعتمد هيكلية واضحة مثل مجلد `apps` للتطبيقات النهائية (اللي بتنشرها) ومجلد `packages` للمكتبات المشتركة. هذا التنظيم بيساعد أي شخص يفهم بنية المشروع بسرعة.
- لا تفرط في المشاركة: مش كل سطر كود لازم يكون في حزمة مشتركة. أحيانًا، تكرار بسيط للكود أفضل من إضافة تعقيد لا داعي له. اسأل نفسك: “هل فعلًا سأحتاج هذا الكود في مكان آخر؟”.
- اهتم بإعدادات الـ CI/CD: سر قوة Turborepo يكمن في الـ Remote Caching. تأكد من إعداد الـ CI/CD pipeline تبعك (مثل GitHub Actions) ليتصل بخدمة تخزين (مثل Vercel أو S3) لمشاركة الكاش بين كل أعضاء الفريق وخوادم النشر. هذا يقلل وقت الـ build من دقائق إلى ثوانٍ.
الخلاصة: من جزر منعزلة إلى قارة متصلة 🚀
الانتقال إلى المستودعات الأحادية (Monorepos) لم يكن مجرد تغيير تقني بالنسبة لفريقنا، بل كان تغييرًا في طريقة تفكيرنا وتعاوننا. تحولنا من مجموعة جزر منعزلة، كل واحدة تتحدث لغة مختلفة، إلى قارة واحدة متصلة، حيث تسير التغييرات بسلاسة، ويتشارك الجميع نفس الأدوات والقوانين.
نعم، هناك منحنى تعلم في البداية، ولكن الفوائد على المدى الطويل في الإنتاجية، جودة الكود، وسهولة الصيانة لا تقدر بثمن. إذا كنت تعاني من نفس المشاكل التي عانينا منها – تضارب الاعتماديات، صعوبة مشاركة الكود، وعمليات النشر المعقدة – فأنصحك بشدة أن تعطي الـ Monorepos فرصة.
الزبدة؟ ابدأ بمشروع صغير، جرب بنفسك، وشوف كيف ممكن هذا النهج يغير طريقة عملك للأفضل. صدقني، لن تندم.