كودك هو حصن: لماذا ستنقذ هندسة Hexagonal مشروع JavaScript الخاص بك
\n\nهل سبق لك أن واجهت موقفًا حيث تحول تغيير قاعدة البيانات أو استبدال API خارجي إلى جحيم من إعادة كتابة نصف المشروع؟ أو ربما كنت تكتب اختبارات وحدة تشبه أكثر اختبارات التكامل، لأن منطق الأعمال كان مرتبطًا بإحكام بـ Express أو MongoDB؟ إذا كان الأمر كذلك - مرحبًا بك في عالم هندسة Hexagonal (Ports and Adapters).
\n\nهذا ليس مجرد نمط عصري من كتب أليستر كوبرن. إنها طريقة عملية لجعل كود JavaScript الخاص بك قويًا، قابلًا للاختبار، ومستقلاً عن الأطر. في هذه المقالة، لن نلقي محاضرات مملة. سنأخذ مهمة حقيقية (نظام إدارة الطلبات) ونبنيها خطوة بخطوة على مبادئ الهندسة السداسية في JavaScript. سترى كيف يظل منطق الأعمال نقيًا، بينما تبقى كل "الضوضاء" (قواعد البيانات، طلبات HTTP، قوائم الانتظار) بالخارج.
\n\nهل أنت مستعد؟ لننطلق!
\n\nما هي هندسة Hexagonal (وما علاقة المنافذ والمهايئات بها)؟
\n\nتخيل بوابة (gateway) لسفينة فضائية. بالداخل - لوحة التحكم (منطق أعمالك). بالخارج - عدد لا نهائي من الكواكب المختلفة (قواعد البيانات، أطر الويب، الخدمات الخارجية). البوابة هي المنفذ (الواجهة). وبدلة الفضاء التي تسمح بالتنفس على كوكب معين هي المهايئ.
\n\nالفكرة الأساسية: نواة التطبيق (core) لا يجب أن تعرف من وكيف يطرق بابها. إنها تعلن فقط عن "منافذ" (واجهات). بينما يتم توصيل التطبيقات المحددة (المهايئات) من الخارج. هذا يمنح ثلاث قدرات خارقة:
\n\n- \n
- عزل منطق الأعمال - تغير قاعدة البيانات دون لمس كود حساب الخصومات. \n
- قابلية الاختبار - تختبر المنطق دون تشغيل Docker وقاعدة بيانات حقيقية. \n
- الاستقلال عن الإطار - يمكنك الانتقال من Express إلى Fastify بمجرد كتابة مهايئ جديد. \n
دعنا ننتقل إلى الكود.
\n\nإعداد المشروع وهيكل المجلدات
\n\nفي البداية، لننشئ هيكلًا بسيطًا. سنكتب بلغة TypeScript النقية (أو JavaScript مع JSDoc - حسب ما يناسبك). هدفنا هو نظام إدارة الطلبات.
\n\nsrc/\n├── core/\n│ ├── domain/ # الكيانات وكائنات القيمة\n│ ├── ports/ # الواجهات (المنافذ)\n│ └── services/ # منطق الأعمال (حالات الاستخدام)\n├── adapters/\n│ ├── primary/ # المهايئات التي تستدعي نواتنا (HTTP, CLI)\n│ └── secondary/ # المهايئات التي تستدعيها النواة (قاعدة بيانات، APIs خارجية)\n└── app.js # نقطة الدخول (تجميع كل شيء معًا)\n\n\nالخطوة 1. النواة: الكيانات المجالية والمنافذ
\n\nلنبدأ بالأهم - بما لا يعتمد على أي شيء. لننشئ كيان Order (طلب).
core/domain/Order.js
\n\nexport class Order {\n constructor({ id, userId, items, status = 'pending' }) {\n this.id = id;\n this.userId = userId;\n this.items = items; // مصفوفة { productId, quantity, price }\n this.status = status;\n this.total = this.calculateTotal();\n }\n\n calculateTotal() {\n return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }\n\n approve() {\n if (this.status !== 'pending') {\n throw new Error('Only pending orders can be approved');\n }\n this.status = 'approved';\n }\n}\n\n\nالآن لنحدد المنافذ - الواجهات التي تتوقعها نواتنا من العالم الخارجي.
\n\ncore/ports/OrderRepository.js
\n\n// هذا منفذ (واجهة). لا ننفذه هنا.\nexport class OrderRepository {\n async save(order) { throw new Error('Not implemented'); }\n async findById(id) { throw new Error('Not implemented'); }\n}\n\n\ncore/ports/EmailService.js
\n\nexport class EmailService {\n async sendOrderConfirmation(order) { throw new Err