لماذا DDD ليس مجرد مصطلح عصري، بل أداة لإنقاذ المشروع
كل مطور جافا واجه مرة على الأقل موقفًا: الكود يعمل، منطق الأعمال منتشر عبر الخدمات، وبعد ستة أشهر لا يستطيع فهم المشروع سوى مؤلفه (وبصعوبة). هل هذا مألوف؟ هذا هو النموذج المجالي الضعيف (anemic domain model) الكلاسيكي - عندما تكون الكيانات مجرد POJO مع دوال جلب (getters) ودوال تعيين (setters)، وكل المنطق يعيش في خدمات "سميكة".
هنا يظهر التصميم القائم على المجال (DDD) - نهج يضع منطق الأعمال في مركز التطوير، وليس التفاصيل التقنية. ولا، هذه ليست منهجية "نظرية" أخرى. DDD في مشاريع جافا يقدم أنماطًا محددة تحول الفوضى إلى بنية معمارية نظيفة وقابلة للصيانة. في هذه المقالة، سنشرح DDD عمليًا: مع كود وأمثلة وبدون حشو.
DDD الاستراتيجي مقابل التكتيكي: أيهما تختار لمشروع جافا؟
ينقسم DDD إلى مستويين: استراتيجي وتكتيكي. الأول يساعد في تقسيم النظام الكبير إلى سياقات محددة (Bounded Contexts) وبناء التواصل بينها. الثاني يقدم لبنات بناء محددة للكود: كيان (Entity)، كائن القيمة (Value Object)، التجميع (Aggregate)، المستودع (Repository)، حدث المجال (Domain Event)، إلخ.
بالنسبة لمطور جافا، DDD التكتيكي هو ما يجب البدء به. إنه يقدم أدوات يمكن تطبيقها اليوم. لكن بدون الفهم الاستراتيجي للسياقات، فإنك تخاطر بإنشاء نموذج "عالمي" لا يناسب أحدًا. لذلك دعونا نبدأ من الصغر: نشرح الأنماط الرئيسية باستخدام مثال مجال بسيط لكن حقيقي - نظام إدارة الطلبات.
لبنات بناء DDD في جافا: من الكيان (Entity) إلى التجميع (Aggregate)
1. الكيان (Entity) - لكل منها هويتها الخاصة
في DDD، الكيان (Entity) هو كائن له معرف فريد وخط حياة مستمر. يمكنك تغيير جميع حقوله، لكنه سيظل نفس الكيان. في جافا، هذا ليس مجرد POJO - إنه كائن ذو سلوك.
مثال: طلب في متجر إلكتروني. حتى لو تغيرت حالة الطلب، فهو نفس الطلب بنفس المعرف.
public class Order { private final OrderId id; // كائن قيمة (Value Object)، ليس نوعًا بدائيًا private OrderStatus status; private List<OrderLineItem> items; private Money totalAmount;
public Order(OrderId id) { this.id = id; this.status = OrderStatus.PENDING; this.items = new ArrayList<>(); this.totalAmount = Money.ZERO; }
public void addItem(Product product, int quantity) { // قاعدة عمل: لا يمكن إضافة منتج إلى طلب مؤكد if (this.status != OrderStatus.PENDING) { throw new IllegalStateException("Cannot add items to a non-pending order"); } OrderLineItem item = new OrderLineItem(product, quantity); items.add(item); recalculateTotal(); }
public void confirm() { if (items.isEmpty()) { throw new IllegalStateException("Cannot confirm an empty order"); } this.status = OrderStatus.CONFIRMED; // هنا يمكن تشغيل حدث مجال // registerEvent(new OrderConfirmedEvent(this.id)); }
private void recalculateTotal() { this.totalAmount = items.stream() .map(OrderLineItem::getSubtotal) .reduce(Money.ZERO, Money::add); }
// دوال الجلب (Getters) - للقراءة فقط، بدون دوال تعيين (Setters)! public OrderId getId() { return id; } public OrderStatus getStatus() { return status; } public List<OrderLineItem> getItems() { return Collections.unmodifiableList(items); } public Money getTotalAmount() { return totalAmount; }}لاحظ: OrderId ليس Long، بل كائن قيمة (Value Object). لماذا؟ لأن النوع البدائي لا يحمل دلالات. OrderId يمكن أن يحتوي على بادئة أو تاريخ إنشاء أو