مقدمة: لماذا المستودع الأحادي في Kotlin؟
\nفي التطوير الحديث باستخدام Kotlin، غالبًا ما تنشأ معضلة: هل يتم تخزين عدة مشاريع مترابطة (الخلفية، الخدمات المصغرة، المكتبات، تطبيقات Android) في مستودعات منفصلة (متعددة المستودعات) أم دمجها في مستودع أحادي واحد. المستودع الأحادي هو استراتيجية لتخزين الكود حيث توجد جميع المكونات في نظام تحكم إصدار واحد. بالنسبة لمشاريع Kotlin، هذا مهم بشكل خاص بفضل نظام البناء القوي Gradle والدعم المدمج للتعددية الوحداتية.
\nفي هذه المقالة، سنناقش الجوانب العملية لتنظيم المستودع الأحادي مع Kotlin: من هيكل الدلائل إلى إعداد CI/CD وإدارة التبعيات. ستتعلم كيفية تجنب الأخطاء الشائعة والحصول على أقصى استفادة من مستودع الكود الموحد.
\n\n1. هيكل المستودع الأحادي: من النظرية إلى التطبيق
\nالهيكل الصحيح هو أساس المستودع الأحادي الناجح. لنفكر في مشروع نموذجي يتضمن REST API على Ktor، مكتبة للعمل مع قاعدة البيانات وتطبيق Android. إليك التسلسل الهرمي الموصى به:
\nmy-monorepo/\n├── build.gradle.kts # ملف البناء الجذري\n├── settings.gradle.kts # إعداد الوحدات\n├── gradle.properties # الخصائص المشتركة\n├── gradle/\n│ └── libs.versions.toml # كتالوج الإصدارات (Version Catalog)\n├── libs/ # المكتبات المشتركة\n│ ├── common-utils/ # الأدوات المساعدة، الامتدادات\n│ ├── common-db/ # العمل مع قاعدة البيانات (Exposed)\n│ └── common-dto/ # كائنات نقل البيانات\n├── services/ # تطبيقات الخادم\n│ ├── user-service/ # خدمة المستخدمين المصغرة\n│ └── order-service/ # خدمة الطلبات المصغرة\n└── apps/ # تطبيقات العميل\n └── android-app/ # تطبيق Android\n\nالعنصر الأساسي هو settings.gradle.kts. يتضمن جميع الوحدات:
rootProject.name = "my-monorepo"\n\ninclude("libs:common-utils")\ninclude("libs:common-db")\ninclude("libs:common-dto")\ninclude("services:user-service")\ninclude("services:order-service")\ninclude("apps:android-app")\n\nيسمح هذا الهيكل بإعادة استخدام الكود بسهولة بين الخدمات والعميل، بالإضافة إلى إدارة إصدارات التبعيات بشكل مركزي.
\n\n1.1. كتالوج الإصدارات: إدارة التبعيات
\nفي المستودع الأحادي، من المهم للغاية تجنب تكرار إصدارات المكتبات. استخدم كتالوج الإصدارات (ملف gradle/libs.versions.toml):
[versions]\nkotlin = "2.0.21"\nktor = "3.0.3"\nexposed = "0.57.0"\n\n[libraries]\nktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }\nexposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }\n\n[plugins]\nkotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }\n\nالآن في أي وحدة يمكنك كتابة:
\ndependencies {\n implementation(libs.ktor.server.core)\n implementation(libs.exposed.core)\n}\n\nيضمن هذا أن جميع الوحدات تستخدم نفس الإصدارات، ويبسط التحديث الشامل.
\n\n2. التنظيم العملي للوحدات: Gradle Multi-Project
\nكل وحدة في المستودع الأحادي هي مشروع فرعي مستقل لـ Gradle مع ملف build.gradle.kts الخاص بها. لننظر في إعداد المكتبة common-utils:
// libs/common-utils/build.gradle.kts\nplugins {\n alias(libs.plugins.kotlin.jvm)\n}\n\ngroup = "com.mycompany.common"\nversion = "1.0.0"\n\ndependencies {\n implementation(kotlin("stdlib"))\n implementation(libs.kotlinx.coroutines)\n}\n\nوهذا مثال على خدمة مصغرة نموذجية على Ktor:
\n// services/user-service/build.gradle.kts\nplugins {\n alias(libs.plugins.kotlin.jvm)\n alias(libs.plugins.ktor)\n}\n\ndependencies {\n implementation(project(":libs:common-utils"))\n implementation(project(":libs:common-dto"))\n impl