في عالم هندسة البرمجيات، تُشكّل جائحة "الكود المعكروني" تهديدًا حقيقيًا. ومع نمو التطبيقات، تجعل المكوّنات المترابطة بشدة الصيانة مؤلمة والاختبار صعبًا. على الرغم من أن البنية التقليدية متعددة الطبقات قد قدّمت بعض الفصل، إلا أنها غالبًا فشلت في عزل منطق الأعمال عن مخاوف البنية التحتية. هنا تبرز البنية السداسية (المعروفة أيضًا بمنافذ ومُكيِّفات). صمّم أليستر كوكبرن هذا النمط لفرض عكس اعتمادية صارم، مما يضمن بقاء منطق الأعمال الأساسي نقيًا وقابلًا للاختبار ومستقلًا عن العالم الخارجي.
المفهوم الأساسي: الشكل السداسي
في مركزها، تتكون البنية السداسية من تطبيق أساسي. يحتوي هذا النواة على قواعد الأعمال وحالات الاستخدام. لا يعرف شيئًا عن قواعد البيانات أو خوادم HTTP أو طوابير الرسائل. بدلاً من ذلك، يحدد واجهات تسمى المنافذ تصف ما يحتاج إلى القيام به (مثل "حفظ الطلب") وما يمكنه القيام به (مثل "إرسال إشعار").
تُحيط بالنواة المُكيِّفات. هذه هي التطبيقات المحددة لتلك المنافذ. قد ينفّذ مُكيِّف قاعدة البيانات منفذ OrderRepository. قد ينفّذ مُكيِّف HTTP منفذ OrderController. والأهم من ذلك، أن سهم الاعتمادية يشير إلى الداخل. تعتمد المُكيِّفات على النواة، وليس العكس أبدًا.
لماذا يهم عكس الاعتمادية
ينص مبدأ عكس الاعتمادية (DIP) على أن الوحدات عالية المستوى يجب ألا تعتمد على الوحدات منخفضة المستوى. يجب أن تعتمد كلتا الوحدتين على التجريدات. في البنية السداسية، "المستوى العالي" هو منطق أعمالك، و"المستوى المنخفض" هو بنيتك التحتية (قاعدة البيانات، واجهة برمجة التطبيقات، واجهة المستخدم). من خلال تحديد المنافذ في النواة، تعكس الاعتمادية. تتكيف البنية التحتية مع منطق الأعمال، وليس العكس. هذا يتيح لك استبدال قاعدة بيانات PostgreSQL بـ MongoDB دون لمس سطر واحد من منطق أعمالك.
تنفيذ عملي في جافا
دعنا ننظر إلى مثال مبسّط يتضمن خدمة طلبات. نحدد منفذًا للاستمرارية وواجهة لحالة الاستخدام.
// النطاق الأساسي - لا توجد اعتماديات خارجية
package com.myapp.core;
public interface OrderRepository {
void save(Order order);
Order findById(String id);
}
public interface ProcessOrderUseCase {
void process(String orderId);
}
تاليًا، ننفّذ منطق حالة الاستخدام، الذي يعتمد فقط على المنفذ المجرد.
// التطبيق الأساسي - يعتمد فقط على المنافذ
package com.myapp.core;
public class ProcessOrderUseCaseImpl implements ProcessOrderUseCase {
private final OrderRepository repository;
public ProcessOrderUseCaseImpl(OrderRepository repository) {
this.repository = repository;
}
@Override
public void process(String orderId) {
Order order = repository.findById(orderId);
if (order == null) throw new RuntimeException("Order not found");
// منطق الأعمال هنا
order.setStatus(Processed);
repository.save(order);
}
}
أخيرًا، نُنشئ مُكيِّفات لتقنيات محددة. تعيش هذه في وحدات أو حزم منفصلة.
// مُكيِّف البنية التحتية - يعتمد على النواة
package com.myapp.infrastructure;
import com.myapp.core.OrderRepository;
import com.myapp.core.Order;
public class JpaOrderRepository implements OrderRepository {
// كود Spring Data JPA أو JPA المحدد هنا
@Override
public void save(Order order) {
// الحفظ في قاعدة البيانات
}
@Override
public Order findById(String id) {
// الجلب من قاعدة البيانات
}
}
قابلية الاختبار والصيانة
أعظم فائدة لهذا الهيكل هي قابلية الاختبار. نظرًا لأن النواة تعتمد فقط على الواجهات، يمكنك كتابة اختبارات وحدة لمنطق أعمالك باستخدام محاكيات أو مسدّات بسيطة، متجاوزًا قاعدة البيانات أو الشبكة تمامًا. يمكنك اختبار سلوك ProcessOrderUseCaseImpl في أجزاء من الثانية، دون عبء الاختبار التكامل.
بالإضافة إلى ذلك، عند تغيّر المتطلبات، يكون الأثر معزولًا. إذا كنت بحاجة إلى إضافة قناة إخراج جديدة، مثل إرسال بريد إلكتروني بعد المعالجة، فتحدد منفذ NotificationPort جديدًا في النواة وتُنشئ EmailAdapter. يبقى المنطق الحالي دون مساس.
الخاتمة
البنية السداسية ليست مجرد مفهوم نظري؛ إنها استراتيجية عملية لبناء برمجيات مرنة. من خلال فصل "ماذا" (منطق الأعمال) عن "كيف" (البنية التحتية)، تُنشئ أنظمة أسهل في الاختبار والصيانة والتطور. على الرغم من أنها تُدخل بعض تكلفة الإعداد الأولية، إلا أن الفوائد طويلة الأجل في وضوح الكود وقابلية التوسع تجعلها استثمارًا يستحق لأي فريق هندسي جاد. ابدأ صغيرًا، حدّد منافذك بعناية، ودع البنية تقود كودك.