السلام عليكم يا جماعة، معكم أخوكم أبو عمر.
قبل كم سنة، كنت شغال على مشروع ضخم، نظام إدارة متكامل لشركة كبيرة. في البداية، كانت الأمور “عال العال”، والميزات الجديدة تنزل بسرعة، والزبون كان مبسوط. كنا فريق صغير ومتحمس، وكل واحد فينا “قد حاله”. لكن مع الوقت، كبر المشروع، وكبر الفريق، وبدأت المشاكل تظهر.
بتذكر مرة، طلب منا الزبون تعديل بسيط جداً، إشي ما بياخد أكتر من ساعة شغل بالمنطق. التعديل كان: “بدنا نغير طريقة حساب الخصم على طلبات العملاء المميزين”. بسيطة، صح؟ اللي صار إنه هالتعديل الصغير أخذ منا أسبوعين كاملين! وكل ما نصلح إشي، يخرب إشي ثاني في مكان ما كنا نتوقعه أبداً. مرة يخرب في نظام الفواتير، ومرة في تقارير المبيعات، ومرة في لوحة تحكم العميل.
في اجتماع من الاجتماعات اللي كلها توتر، واحد من الزملاء صرخ وحكى: “يا جماعة الكود صار زي كرة الطين العملاقة، كل ما تحاول تشكلها بتخرب أكتر وبتوسخ إيديك!”. هاي الجملة، “كرة الطين العملاقة” أو الـ “Big Ball of Mud” زي ما بسموها بالأجنبي، وصفت حالتنا بالضبط. الكود كان متشابك، منطق العمل (Business Logic) موزع في كل مكان، وكل جزء بيعتمد على عشرين جزء ثاني بطرق غريبة. وصلنا لمرحلة صرنا نخاف نكتب سطر كود جديد.
في ليلة من الليالي وأنا بحاول أصلح بج (Bug) غريب ظهر بعد التعديل إياه، قررت إنه “لهون وبس”. مش معقول نكمل هيك. لازم يكون في طريقة أحسن. ومن هون بدأت رحلتي مع عالم اسمه “التصميم الموجه بالمجال” أو Domain-Driven Design (DDD).
ما هي ‘كرة الطين العملاقة’ (Big Ball of Mud) ولماذا هي كابوس المبرمجين؟
قبل ما نحكي عن الحل، خلينا نوصف المشكلة أكتر. “كرة الطين” مش مجرد كود مش نظيف، هي حالة من الفوضى المعمارية الكاملة. من أهم أعراضها:
- تشابك الكود (Spaghetti Code): كل شيء يعتمد على كل شيء. تغيير صغير في مكان يؤدي إلى انهيارات غير متوقعة في أماكن أخرى.
- منطق الأعمال المبعثر: جزء من قواعد العمل موجود في واجهة المستخدم، وجزء في قاعدة البيانات على شكل (Stored Procedures)، وجزء في الكود الخلفي، بس وين بالضبط؟ الله أعلم!
- صعوبة فهم النظام: المبرمج الجديد اللي بنضم للفريق بيحتاج شهور بس ليفهم “الطبخة” كيف ماشية، ولو فهمها أصلاً.
- بطء التطوير: إضافة أي ميزة جديدة بتصير مشروع قائم بحد ذاته، وبتاخد وقت وجهد أضعاف المفروض.
هاي الحالة بتصير بشكل طبيعي في المشاريع اللي بتبدأ صغيرة وبتكبر بسرعة بدون وجود رؤية معمارية واضحة من البداية. بنكون مستعجلين نسلم الشغل، وبنعمل حلول سريعة “تمشي الحال”، ومع الوقت هاي الحلول بتتراكم وبتخنق المشروع.
الضوء في آخر النفق: التصميم الموجه بالمجال (DDD) كطوق نجاة
الـ DDD مش مكتبة أو إطار عمل (Framework) بتنزله وبتستخدمه. الـ DDD هو “فلسفة” أو “منهجية” في التفكير وتصميم البرمجيات. جوهر الفكرة بسيط جداً: خلي الكود تبعك يعكس بشكل مباشر وحقيقي منطق العمل (Business Domain) اللي بتحاول تحل مشكلته.
يعني بدل ما يكون همنا الأول والأخير هو الجداول في قاعدة البيانات أو الـ API Endpoints، بصير همنا هو فهم “البزنس” نفسه. شو يعني “عميل”؟ شو يعني “طلب”؟ شو العمليات اللي بتصير على “الطلب”؟ شو القواعد اللي بتحكم هاي العمليات؟ لما نفهم هاي الأمور بعمق، بنقدر نبني كود “بحكي لغة البزنس”.
وهون مربط الفرس. الـ DDD بيعطينا مجموعة من الأدوات والمفاهيم اللي بتساعدنا نعمل هاد الإشي بشكل منظم.
أساسيات الـ DDD.. مش بس حكي نظري!
لما بدينا نطبق الـ DDD، ما كان الموضوع سهل. احتجنا نغير طريقة تفكيرنا بالكامل. هاي أهم المفاهيم اللي كانت نقطة التحول بالنسبة إلنا:
اللغة المشتركة (Ubiquitous Language): كيف نتحدث بنفس اللغة؟
أول خطوة وأهم خطوة. الفكرة هي إنه لازم يكون في لغة واحدة، واضحة، ومحددة، بيستخدمها كل حدا في المشروع: المبرمجين، مدراء المشاريع، خبراء المجال (Domain Experts)، والزبون نفسه.
في مشروعنا القديم، جماعة البزنس كانوا يحكوا “زبون”، والمبرمجين عاملين كلاس اسمه `User`، وفي قاعدة البيانات الجدول اسمه `Account`. هاي اللخبطة بتخلق سوء فهم ومشاكل لا حصر لها.
مع الـ DDD، قعدنا مع خبراء البزنس، واتفقنا: لما نحكي “عميل” (Customer)، إحنا بنقصد الكيان اللي بيعمل طلبات شراء. ولما نحكي “مستخدم” (User)، بنقصد أي حدا عنده حساب على النظام، ممكن يكون موظف خدمة عملاء أو مدير. هذا التعريف صار مقدس. بنستخدمه في كلامنا، في اجتماعاتنا، في توثيقنا، وفي أسماء الكلاسات والمتغيرات في الكود.
نصيحة أبو عمر: اعمل ورشة عمل اسمها “Event Storming”. اجمع المبرمجين وخبراء البزنس في غرفة واحدة، وحط على الحيط ورق كبير (Whiteboard أو Post-it notes). اطلب من خبراء البزنس يوصفوا العمليات اللي بتصير خطوة بخطوة. كل حدث (Event) بيصير اكتبه على ورقة (مثال: “تم إنشاء الطلب”، “تم الدفع”، “تم شحن الطلب”). هاي الورشة بتطلعلك كنوز: بتكتشف اللغة المشتركة، العمليات، والقواعد بشكل ما كنت تحلم فيه.
السياقات المحدودة (Bounded Contexts): لكل مقام مقال ولكل سياق كود!
هذا هو السلاح السري ضد “كرة الطين”. بدل ما يكون عندك نظام واحد ضخم ومتشابك، الـ DDD بشجعك تقسم نظامك لعدة “سياقات” أصغر ومستقلة. كل سياق له مسؤولية محددة، وله لغته المشتركة الخاصة فيه.
مثلاً، في نظام التجارة الإلكترونية تبعنا، كلمة “منتج” (Product) إلها معاني مختلفة:
- في سياق الكتالوج (Catalog Context): المنتج هو عبارة عن اسم، وصف، صور، وسعر.
- في سياق المخزون (Inventory Context): المنتج هو عبارة عن رمز (SKU) وكمية متوفرة في المستودع.
- في سياق الشحن (Shipping Context): المنتج هو عبارة عن وزن وأبعاد عشان حساب تكلفة الشحن.
بدل ما نعمل كلاس `Product` عملاق فيه كل هاي الخصائص، أنشأنا ٣ نماذج (Models) مختلفة للمنتج، كل واحد في سياقه الخاص. هذا سمح لكل فريق يشتغل على سياقه باستقلالية وبدون ما “يخبط” في شغل الفرق الثانية. هاي الفكرة هي أساس الـ Microservices بالمناسبة.
الكيانات (Entities) وكائنات القيمة (Value Objects): الفرق الجوهري
داخل كل سياق، لازم نميز بين نوعين من الكائنات:
- الكيان (Entity): هو كائن له هوية مميزة ومستمرة عبر الزمن. الهوية هي الأهم، حتى لو تغيرت خصائصه. مثلاً، “الطلب” (Order) هو كيان. طلب رقم 105 بضل هو نفسه حتى لو تغيرت حالته من “قيد التنفيذ” إلى “تم الشحن”. أهم إشي فيه هو الـ ID تبعه.
- كائن القيمة (Value Object): هو كائن يتم تعريفه بقيمه فقط، وليس له هوية. مثلاً، “العنوان” (Address). لو عندك عنوانين بنفس الشارع والمدينة والرمز البريدي، فهم متطابقين. ما بهمك مين انعمل أول. كائنات القيمة غالباً بتكون (Immutable)، يعني ما بتقدر تعدل عليها بعد ما تنشئها، إذا بدك تغيرها، بتعمل وحدة جديدة.
// مثال بسيط جداً للتوضيح
// العنوان ككائن قيمة (Value Object)
public class Address : ValueObject
{
public string Street { get; private set; }
public string City { get; private set; }
public string Country { get; private set; }
// لا يوجد ID هنا!
// يتم تعريفه بقيمه فقط
}
// العميل ككيان (Entity)
public class Customer : Entity
{
public Guid Id { get; private set; } // الهوية هي الأهم
public string Name { get; private set; }
public Address ShippingAddress { get; private set; }
public void ChangeAddress(Address newAddress)
{
// ...
this.ShippingAddress = newAddress;
}
}
التجمعات (Aggregates): حارس البوابة لقواعد العمل
هذا المفهوم عبقري وحلّ إلنا مشاكل كتير. الـ Aggregate هو مجموعة من الكيانات وكائنات القيمة اللي بنعاملها كوحدة واحدة متكاملة. كل Aggregate بكون إله “جذر” (Root)، وهو كيان بنستخدمه كمدخل وحيد للتعامل مع كل الأجزاء داخل الـ Aggregate.
ليش؟ عشان نحافظ على “تناسق البيانات” (Consistency) ونطبق قواعد العمل (Business Rules) بشكل صارم.
مثال: “الطلب” (Order) هو جذر التجمع (Aggregate Root). الـ Aggregate نفسه بيحتوي على “أصناف الطلب” (OrderItems). أي حدا بده يضيف صنف جديد للطلب، ما بيقدر يروح مباشرة على قائمة الأصناف ويضيفه. لازم يمر من خلال جذر التجمع، يعني من خلال كلاس الـ `Order` نفسه.
public class Order : AggregateRoot
{
public Guid Id { get; private set; }
private readonly List _items = new List();
public IReadOnlyCollection Items => _items.AsReadOnly();
public void AddItem(Guid productId, decimal price, int quantity)
{
// قاعدة عمل 1: لا يمكن أن يكون الطلب فارغاً
if (quantity i.ProductId == productId))
{
throw new BusinessRuleException("المنتج موجود مسبقاً في الطلب.");
}
var orderItem = new OrderItem(productId, price, quantity);
_items.Add(orderItem);
}
}
// الكود الخارجي لا يستطيع تعديل قائمة الأصناف مباشرة
// order.Items.Add(something) // هذا السطر سيعطي خطأ، وهذا هو المطلوب!
// الطريقة الصحيحة (والوحيدة) هي من خلال جذر التجمع
// order.AddItem(productId, price, quantity);
شايفين كيف؟ كلاس الـ `Order` صار هو “حارس البوابة” لقواعد العمل الخاصة بالطلب. ما في حدا بيقدر يتلاعب بالبيانات تبعته من برا. شغل نظيف ومرتب.
نصائح أبو عمر الذهبية (من قلب المعركة)
- الـ DDD مش للمشاريع الصغيرة والبسيطة: على بلاطة، إذا بتعمل موقع شخصي أو تطبيق بسيط، تطبيق الـ DDD بكون زيادة شغل على الفاضي (Over-engineering). الـ DDD بيلمع وبيظهر قوته في الأنظمة الكبيرة والمعقدة اللي فيها منطق عمل غني.
- ابدأ بالسياق الأهم (Core Domain): مش ضروري تطبق الـ DDD على كل النظام. ركز على الجزء اللي فيه أكبر قيمة للبزنس واللي فيه التعقيد الأكبر. باقي الأجزاء (زي نظام تسجيل الدخول مثلاً) ممكن تضل بسيطة.
- خبراء المجال هم أصدقاؤك: لا تفترض إنك فاهم البزنس. اقضِ وقت مع الناس اللي بيشتغلوا في البزنس نفسه يومياً. اسمع منهم، تعلم لغتهم، وخليهم جزء من عملية التصميم.
- الـ DDD رحلة، مش وجهة: فهمك للمجال بتطور مع الوقت. لا تخاف تعيد تصميم بعض الأجزاء (Refactoring) لما تكتشف فهم أعمق للمجال. الكود هو مجرد انعكاس لفهمنا الحالي.
الخلاصة: هل الـ DDD هو الحل السحري لكل المشاكل؟
لا، أكيد لا يوجد حل سحري في عالم البرمجة. التصميم الموجه بالمجال هو أداة قوية جداً، لكنها زي أي أداة، لازم تعرف متى وكيف تستخدمها. تطبيقها يتطلب استثماراً كبيراً في الوقت والجهد في مرحلة التحليل والتصميم، ويتطلب فريقاً عنده النضج التقني الكافي ليفهم المبادئ ويطبقها صح.
لكن بالنسبة إلنا، في مشروع “كرة الطين” ذاك، كان الـ DDD هو طوق النجاة الحقيقي. حولنا الفوضى إلى نظام، والغموض إلى وضوح. صرنا نسلم الميزات الجديدة بثقة وسرعة أكبر، والنقاشات مع جماعة البزنس صارت أسهل وأكثر إنتاجية. والأهم من كل هاد، رجعنا نحب الشغل اللي بنعمله ونستمتع فيه.
إذا كنت بتعاني من نفس الأعراض اللي حكيت عنها في بداية المقال، بنصحك بشدة تبدأ تقرأ وتتعلم عن الـ DDD. ممكن يكون هو المفتاح اللي حيغير طريقة شغلك للأفضل. بالتوفيق يا جماعة! 🚀