يا جماعة الخير، السلام عليكم ورحمة الله. اسمي أبو عمر، وأنا اليوم جاي أحكي لكم قصة صارت معي قبل كم سنة، قصة غيرت نظرتي لبناء البرمجيات بشكل كامل.
كنا قاعدين في اجتماع مع فريق التسويق والعمليات في شركة كنت أعمل فيها. كنا نطور نظامًا لإدارة الحملات الإعلانية. الاجتماع كان، بصراحة، جحيم مصغّر. مدير التسويق يحكي عن “دورة حياة العميل” و”معدل التحويل”، واحنا كفريق برمجة بنحكي عن “جداول” و”API endpoints” و”services”. كل واحد في واد. أتذكر مدير التسويق سألني سؤال بسيط: “أبو عمر، هل يمكننا تمديد فترة العرض الترويجي للعملاء الجدد؟”
جوابي كان كارثيًا. قلت له: “نعم، نحتاج لتعديل دالة updateUserCampaignStatus في خدمة CampaignService، ونضيف شرطًا على حقل creation_date في جدول users مع تغيير قيمة status_flag إلى 4″.
الرجل نظر إليّ وكأني أتحدث بلغة من كوكب آخر. صمت للحظة ثم قال: “يعني… آه ولا لأ؟”. في تلك اللحظة، أدركت أن المشكلة ليست تقنية. المشكلة كانت أعمق بكثير: كودنا لا يفهم طبيعة عملنا. كان مجرد مجموعة من التعليمات التقنية التي لا تعكس الواقع الذي نحاول نمذجته. ومن هنا بدأت رحلتي مع ما يُعرف بـ “التصميم الموجه بالمجال” أو Domain-Driven Design (DDD).
ما هو التصميم الموجه بالمجال (DDD)؟ ولماذا يجب أن نهتم؟
دعوني أبسط الأمر. تخيل أنك تبني بيتًا. هل تتحدث مع المهندس المعماري بلغة “أريد 1000 طوبة هنا و 50 مترًا من أنابيب PVC هناك”؟ بالطبع لا. أنت تتحدث معه بلغة “أريد مطبخًا واسعًا، وغرفة معيشة تطل على الحديقة، وحمامين”. أنت تصف “المجال” (البيت)، وهو يترجم هذا إلى تفاصيل تقنية (طوب، أنابيب، أسمنت).
التصميم الموجه بالمجال (DDD) هو نفس الفلسفة ولكن في عالم البرمجيات. إنه نهج يركز على أن يكون تصميم وهيكل الكود البرمجي مرآة دقيقة للمجال الذي يخدمه (Business Domain). الهدف هو أن يصبح الكود مفهومًا ليس فقط للمبرمجين، بل أيضًا لخبراء المجال وأصحاب المصلحة.
الفائدة الكبرى؟ عندما يتغير منطق العمل (Business Logic)، وهو يتغير دائمًا، يصبح التعديل على الكود أسهل وأكثر أمانًا، لأن الكود منظم حول مفاهيم العمل نفسها.
حجر الأساس: اللغة المشتركة (Ubiquitous Language)
هذا هو قلب الـ DDD النابض. اللغة المشتركة هي لغة واحدة، واضحة، يتفق عليها فريق التطوير وخبراء المجال ويستخدمونها في كل مكان: في الاجتماعات، في المستندات، في أسماء المتغيرات والدوال والكلاسات في الكود.
لنعد لمثال الاجتماع الكارثي. بدلًا من لغتي التقنية المعقدة، كان يجب أن تكون المحادثة والكود يعكسان لغة مشتركة.
- مصطلح العمل: “تمديد العرض الترويجي”
- في الكود (باستخدام اللغة المشتركة): يمكن أن يكون لدينا كلاس اسمه
PromotionalOfferيحتوي على دالة اسمهاExtendOffer(newEndDate).
لاحظ الفرق؟ الآن، عندما يطلب مدير التسويق تمديد العرض، يمكن للمبرمج أن يقول بثقة: “بالتأكيد، سنقوم باستدعاء دالة ExtendOffer على العرض الحالي”. الجميع يفهم، والجميع على نفس الصفحة.
نصيحة من أبو عمر: ابدأ بإنشاء معجم (Glossary) مشترك. وثّق فيه كل مصطلح من مصطلحات العمل وما يعنيه بالضبط. اجعل هذا المعجم في مكان مركزي (مثل Wiki) واجعله المصدر الوحيد للحقيقة. هذه الخطوة وحدها ستصنع فرقًا هائلاً.
أدوات البناء في عالم DDD: التصميم الاستراتيجي والتكتيكي
يقسم الـ DDD إلى جزأين رئيسيين يساعداننا في تنظيم هذا التعقيد: التصميم الاستراتيجي (الصورة الكبيرة) والتصميم التكتيكي (التفاصيل الدقيقة داخل الكود).
1. التصميم الاستراتيجي: رسم حدود الملعب
قبل أن نكتب أي كود، يجب أن نفهم الخريطة العامة للمشروع. أهم مفهوم هنا هو:
السياق المحدود (Bounded Context)
السياق المحدود هو ببساطة حدود واضحة يطبق داخلها نموذج معين ولغة مشتركة معينة. المصطلح الواحد قد يعني أشياء مختلفة في سياقات مختلفة.
مثال عملي: في نظام تجارة إلكترونية، كلمة “منتج” (Product) لها معانٍ مختلفة:
- في سياق المبيعات (Sales Context): المنتج له سعر، وصف، وصور. همه الأول هو إقناع العميل بالشراء.
- في سياق المخزون (Inventory Context): نفس المنتج له رقم SKU، موقع في المستودع، وكمية متاحة. همه هو التتبع اللوجستي.
- في سياق الدعم الفني (Support Context): المنتج له دليل استخدام، وأسئلة شائعة. همه هو مساعدة العميل بعد الشراء.
بدلًا من إنشاء كلاس Product عملاق وفوضوي يخدم الجميع، نقوم بإنشاء نماذج منفصلة لكل سياق. هذا يمنع الكود من أن يصبح “كرة طين كبيرة” (Big Ball of Mud) ويسمح لكل فريق بالتركيز على سياقه الخاص.
2. التصميم التكتيكي: بناء القطع الداخلية
هنا ندخل في تفاصيل الكود. هذه هي الأدوات التي نستخدمها لبناء نموذجنا داخل كل سياق محدود.
الكيانات (Entities)
هي كائنات لها هوية فريدة ومستمرة عبر الزمن. حتى لو تغيرت كل خصائصها، تظل هي نفسها. مثل “العميل” (له رقم هوية) أو “الطلب” (له رقم طلب).
// مثال بسيط لكيان الطلب
public class Order : IEntity
{
public Guid Id { get; private set; }
public Guid CustomerId { get; private set; }
private List<OrderItem> _orderItems = new List<OrderItem>();
// ... منطق العمل الخاص بالطلب يوضع هنا
public void AddItem(Product product, int quantity)
{
if (quantity <= 0)
{
throw new InvalidOperationException("Quantity must be positive.");
}
// ... المزيد من القواعد والتحققات
_orderItems.Add(new OrderItem(product.Id, quantity, product.Price));
}
}
كائنات القيمة (Value Objects)
هي كائنات تُعرَّف بقيمها وخصائصها، وليس بهويتها. هي غير قابلة للتغيير (Immutable). مثل “العنوان” أو “المال” (مبلغ 50 دولارًا هو نفسه أي مبلغ 50 دولارًا آخر).
// مثال لكائن قيمة يمثل المال
public class Money : ValueObject
{
public decimal Amount { get; private set; }
public string Currency { get; private set; }
public Money(decimal amount, string currency)
{
// ... تحققات هنا
Amount = amount;
Currency = currency;
}
// ... عمليات مثل الجمع والطرح، والتي تُرجع كائن Money جديد
public Money Add(Money other)
{
if (this.Currency != other.Currency)
{
throw new InvalidOperationException("Cannot add different currencies.");
}
return new Money(this.Amount + other.Amount, this.Currency);
}
}
الفائدة الكبرى من عدم قابليتها للتغيير هي الأمان. يمكنك تمريرها في كل مكان دون الخوف من أن يقوم جزء ما من النظام بتغييرها بشكل غير متوقع.
التجمعات (Aggregates)
هذا المفهوم عبقري. التجمع هو مجموعة من الكيانات وكائنات القيمة التي نتعامل معها كوحدة واحدة متكاملة. الهدف منه هو الحفاظ على تناسق البيانات (Consistency).
- لكل تجمع “جذر” (Aggregate Root)، وهو الكيان الرئيسي في التجمع.
- أي تعامل مع الكائنات داخل التجمع يجب أن يمر حصرًا عبر الجذر. لا يمكنك تعديل عنصر داخل الطلب (OrderItem) مباشرة، بل يجب أن تطلب من كائن الطلب (Order) أن يقوم بذلك.
في مثالنا السابق، Order هو الجذر، و OrderItem هي كيانات داخل التجمع. هذا يضمن أن كل قواعد العمل (مثل حساب المجموع الكلي) يتم تطبيقها دائمًا.
المستودعات (Repositories)
هي واجهة برمجية تخفي تفاصيل تخزين واسترجاع التجمعات (Aggregates). الكود الخاص بالعمل يتعامل مع المستودع كأنه مجموعة من الكائنات في الذاكرة، دون أن يعرف هل هي مخزنة في قاعدة بيانات SQL، أو NoSQL، أو حتى ملف نصي.
public interface IOrderRepository
{
Order GetById(Guid orderId);
void Save(Order order);
}
// الكود في طبقة العمل (Application Layer) يستخدمها هكذا:
var order = orderRepository.GetById(someId);
order.AddItem(someProduct, 2);
orderRepository.Save(order);
لاحظ كيف أن منطق العمل لا يحتوي على أي جمل SQL أو تفاصيل عن قاعدة البيانات.
الخلاصة: من الفوضى إلى الوضوح 💡
يا جماعة، التحول إلى التفكير بمنهجية DDD ليس مجرد تغيير تقني، بل هو تغيير ثقافي في طريقة بناء البرمجيات. إنه ينقلنا من كتابة “كود يعمل” إلى بناء “أصول برمجية تعكس العمل”.
الرحلة لم تكن سهلة، وتطلبت الكثير من النقاشات والتعلم، ولكن النتيجة كانت مذهلة. أصبحنا قادرين على إضافة ميزات جديدة بسرعة وثقة، وأصبح المبرمجون الجدد يفهمون النظام بشكل أسرع، والأهم من ذلك، أصبحنا نحن ومدراء العمل نتحدث نفس اللغة أخيرًا.
نصيحتي الأخيرة لك: لا تخف من DDD. لا تحاول تطبيق كل شيء دفعة واحدة. ابدأ بالشيء الأهم: اللغة المشتركة. ابدأ بالاستماع أكثر لخبراء المجال في شركتك. اجلس معهم، اشرب فنجان قهوة، وافهم عالمهم. ستجد أن الكود الذي تكتبه سيصبح أفضل وأكثر قيمة. صدقني، الأمر يستحق العناء. 🚀