مستودع أحادي مع Rust: تنظيم الكود عمليًا
\n\nعند تطوير مشاريع كبيرة بلغة Rust، يطرح السؤال عاجلاً أم آجلاً: كيف ننظم الكود بحيث يبقى قابلاً للصيانة وإعادة الاستخدام وقابلًا للتوسع بسهولة؟ أحد الأساليب الشائعة هو المستودع الأحادي (monorepo). في هذه المقالة، سنشرح ما هو المستودع الأحادي في سياق Rust، وكيفية إعداده باستخدام Cargo Workspaces وما هي الأساليب العملية التي ستساعد في تجنب الفوضى في قاعدة الكود.
ستكون المادة مفيدة لكل من المبتدئين الذين بدأوا للتو العمل مع المشاريع الكبيرة، والمطورين ذوي الخبرة الذين يبحثون عن طرق فعالة لتنظيم الكود.
\n\nما هو المستودع الأحادي ولماذا هو مطلوب في Rust؟
\n\nالمستودع الأحادي (monorepo) هو نهج يتم فيه تخزين جميع كود المشروع (أو عدة مشاريع مرتبطة) في مستودع واحد. على عكس المستودعات المتعددة (multirepos)، حيث يعيش كل خدمة أو مكتبة في مستودع Git منفصل.
\n\nلماذا يختار مطورو Rust غالبًا المستودع الأحادي؟
\n- \n
- إصدار موحد للتبعيات. تتجنب التعارضات عندما تتطلب صناديق (crates) مختلفة إصدارات مختلفة من نفس المكتبة. \n
- إعادة هيكلة مبسطة. تغيير API في صندوق واحد يظهر فورًا في جميع الصناديق التابعة له — سيشير مترجم Rust إلى جميع الأماكن التي تحتاج إلى تعديل. \n
- الترجمة المشتركة. يمكن لـ Cargo تحسين عملية البناء عن طريق إعادة استخدام القطع المترجمة بالفعل. \n
- تاريخ تغييرات موحد. لست بحاجة إلى مزامنة الالتزامات (commits) بين مستودعات مختلفة. \n
الأداة الرئيسية لتنظيم المستودع الأحادي في Rust هي Cargo Workspaces. دعنا نرى كيفية استخدامها عمليًا.
\n\nإعداد Cargo Workspaces: مثال خطوة بخطوة
\n\nلنفترض أننا ننشئ تطبيقًا يتكون من ثلاثة أجزاء:
\n- \n
- مكتبة
core_lib— المنطق العام؛ \n - مكتبة
utils— الأدوات المساعدة؛ \n - ملف قابل للتنفيذ
app— الملف الثنائي الرئيسي. \n
لننشئ هيكل المستودع الأحادي:
\n\nmy_monorepo/\n├── Cargo.toml # بيان workspace الجذر\n├── core_lib/\n│ ├── Cargo.toml\n│ └── src/\n│ └── lib.rs\n├── utils/\n│ ├── Cargo.toml\n│ └── src/\n│ └── lib.rs\n└── app/\n ├── Cargo.toml\n └── src/\n └── main.rs\n\n\nالخطوة 1. ملف Cargo.toml الجذر
[workspace]\nmembers = [\n "core_lib",\n "utils",\n "app",\n]\n\n\nالآن جميع الصناديق الثلاثة هي جزء من workspace واحد. سيقوم Cargo بإدارتها ككيان واحد.
\n\nالخطوة 2. التبعيات الداخلية
\nفي ملف app/Cargo.toml، نحدد أنه يعتمد على مكتباتنا:
[package]\nname = "app"\nversion = "0.1.0"\nedition = "2021"\n\n[dependencies]\ncore_lib = { path = "../core_lib" }\nutils = { path = "../utils" }\n\n\nالخطوة 3. كتابة الكود
\nمثال على كود في core_lib/src/lib.rs:
pub fn greet(name: &str) -> String {\n format!("Hello, {}!", name)\n}\n\n\nفي app/src/main.rs نستخدم هذه الدالة:
use core_lib::greet;\n\nfn main() {\n println!("{}", greet("Rust Developer"));\n}\n\n\nالخطوة 4. البناء والتشغيل
\nالأوامر cargo build و cargo test و cargo run تعمل على workspace بأكمله. يمكن أيضًا تشغيل صندوق معين:
cargo run -p app\ncargo test -p core_lib\n\n\nهذا هو الإعداد الأساسي. الآن ننتقل إلى الممارسات الأكثر تقدمًا.
\n\nتنظيم متقدم: الوحدات (modules)، الاختبارات وإدارة الإصدارات
\n\nعندما يزداد عدد الصناديق، من المهم الالتزام بالانضباط. إليك بعض النصائح العملية: