الهندسة السداسية في Kotlin: عندما يتنفس الكود بحرية
\n\nتخيل أنك تبني منزلاً. يمكنك صب الأساس مباشرة، ورفع الجدران، وتركيب النوافذ. أو يمكنك أولاً تصميم الهيكل، ومد المرافق، وبعدها فقط - الاهتمام بالتشطيب. في عالم البرمجة، يُسمى النهج الثاني الهندسة السداسية (وتعرف أيضًا بـ Ports & Adapters). وKotlin بفضل إيجازه ونظام الأنواع القوي - هي اللغة المثالية لتجسيد هذا المفهوم على أرض الواقع.
\n\nهذه المقالة ليست كتابًا دراسيًا مملًا. إنه دليل عملي يوضح كيف الهندسة السداسية في Kotlin تحول كودك إلى نظام مرن، قابل للاختبار، ومستقل عن الخدمات الخارجية. سنحلل مثالًا من الصفر، ونكتب كودًا حقيقيًا، ونرى كيف يعمل ذلك عمليًا.
\n\nما هي الهندسة السداسية ولماذا تحتاجها؟
\n\nأليستر كوكبيرن، الذي ابتكر هذا المصطلح، شبه التطبيق بشكل سداسي (هيكساجون). في المنتصف - منطق الأعمال الخاص بك. على الحواف - المنافذ (واجهات) التي يتواصل من خلالها مع العالم الخارجي. المحولات هي تطبيقات لهذه الواجهات لتقنيات محددة: قاعدة بيانات، إطار ويب، قائمة انتظار الرسائل.
\n\nالفكرة الأساسية: منطق الأعمال لا يعرف شيئًا عن كيفية تخزين البيانات أو أي إطار عمل يُستخدم. إنه ببساطة يستدعي طرق المنافذ. هل تريد استبدال PostgreSQL بـ MongoDB؟ فقط اكتب محولًا جديدًا. منطق الأعمال لن يلاحظ حتى التبديل.
\n\nفي Kotlin، هذا مناسب بشكل خاص بفضل الواجهات ذات التطبيقات الافتراضية، الفئات المختومة (sealed classes)، والأسلوب الوظيفي. دعنا ننتقل إلى العمل.
\n\nتصميم النظام: مثال متجر إلكتروني
\n\nلنفترض أننا نكتب خدمة مصغرة لإدارة الطلبات. في المركز - OrderService، الذي ينشئ الطلبات ويتحقق من توفر المنتجات. العالم الخارجي - وحدة تحكم HTTP وقاعدة بيانات.
إليك هيكل المشروع:
\n\nsrc/\n├── domain/ # النواة (منطق الأعمال)\n│ ├── model/\n│ ├── port/\n│ └── service/\n├── adapter/\n│ ├── input/ # المحولات الواردة (وحدات التحكم)\n│ └── output/ # المحولات الصادرة (المستودعات)\n└── Application.kt # نقطة الدخول\n\n\nالخطوة 1. النواة: النماذج والمنافذ
\n\nلنبدأ بنموذج الطلب. في Kotlin سيكون data class:
// domain/model/Order.kt\ndata class Order(\n val id: String,\n val customerId: String,\n val items: List<OrderItem>,\n val status: OrderStatus\n)\n\nenum class OrderStatus {\n PENDING, CONFIRMED, SHIPPED, CANCELLED\n}\n\ndata class OrderItem(\n val productId: String,\n val quantity: Int\n)\n\n\nالآن سنحدد المنافذ - الواجهات التي تتواصل من خلالها النواة مع العالم الخارجي. سيكون لدينا منفذان: واحد للحصول على بيانات المنتجات، والثاني لحفظ الطلبات.
\n\n// domain/port/ProductRepository.kt\ninterface ProductRepository {\n fun checkAvailability(productId: String, quantity: Int): Boolean\n}\n\n// domain/port/OrderRepository.kt\ninterface OrderRepository {\n fun save(order: Order): Order\n fun findById(id: String): Order?\n}\n\n\nلاحظ: لا يوجد ذكر لقاعدة بيانات أو HTTP أو أطر عمل. منطق أعمال نقي.
\n\nالخطوة 2. خدمة الأعمال
\n\nالآن سنكتب OrderService الذي يستخدم هذه المنافذ. في Kotlin، نمرر التبعيات عبر المُنشئ - وهذا يُسمى حقن التبعيات (DI).
// domain/service/OrderService.kt\nclass OrderService(\n private val productRepository: ProductRepository,\n private val orderRepository: OrderRepository\n) {\n fun createOrder(customerId: String, items: List<OrderItem>): Order {\n // نتحقق من توفر كل منتج\n for (item in items) {\n if (!productRepository.checkA