التصميم القائم على المجال في C#: كيف لا تغرق في التعقيد
\n\nعندما يتجاوز مشروع C# الخاص بك مرحلة "تطبيق CRUD البسيط"، تأتي لحظة الحقيقة. يبدأ منطق الأعمال في التشابك تحت الأقدام، وتنمو أساليب الخدمات إلى مئات الأسطر، وتسبب كلمة "إعادة الهيكلة" رعشة عصبية. هل هذا مألوف؟ مرحبًا بك في عالم حيث بدون التصميم القائم على المجال (DDD) فإنك تخاطر بتحويل الكود إلى مستنقع.
\n\nDDD ليس مجرد "إطار عمل آخر". إنها فلسفة تضع مجال الأعمال في مركز الكون. اليوم سنشرح كيفية تطبيق التصميم القائم على المجال في مشاريع C# بحيث يصبح الكود ليس فقط عمليًا، بل مفهومًا ومرنًا. لا ماء - فقط أمثلة حقيقية ونصائح عملية.
\n\nلماذا ينقذ DDD مشاريع C#؟
\n\nتخيل أنك تكتب نظامًا لإدارة الطلبات في متجر إلكتروني. إذا وضعت كل المنطق في وحدات التحكم ASP.NET، فبعد ستة أشهر لن تستطيع شرح لماذا تستدعي طريقة CreateOrder() طريقة CalculateShipping()، والتي بدورها تصل إلى قاعدة البيانات لأسعار الصرف. يقدم DDD فصلًا واضحًا: هناك طبقة المجال حيث يعيش منطق الأعمال، وهناك البنية التحتية التي تهتم بالتفاصيل التقنية.
الفكرة الأساسية لـ DDD هي اللغة الشاملة (Ubiquitous Language). أنت وخبراء الأعمال تتحدثون بلغة واحدة. في الكود، يتم التعبير عن ذلك بمصطلحات مثل: "Order"، "LineItem"، "DiscountPolicy"، وليس "OrderDto"، "OrderService" و "Helper". كلما كان الكود أقرب إلى لغة الأعمال، كان من الأسهل صيانته.
\n\nالتصميم الاستراتيجي والتكتيكي
\n\nينقسم DDD إلى جزئين: استراتيجي (كيفية تقسيم النظام إلى وحدات) وتكتيكي (كيفية كتابة الكود داخل الوحدة). في C#، يمنحنا التصميم التكتيكي أربع لبنات بناء قوية:
\n\n- \n
- Entity — كائن بمعرف فريد (مثل
OrderId). \n - Value Object — كائن يُعرف بخصائصه (مثل
MoneyأوAddress). \n - Aggregate Root — الكيان الجذري الذي يدير مجموعة من الكائنات. \n
- Repository — تجريد للوصول إلى البيانات. \n
دعنا نرى كيف يبدو هذا في الكود.
\n\nمثال: تصميم طلب (Order)
\n\nلنبدأ بسيطًا: لدينا طلب يتكون من بنود. يمكن أن يكون الطلب في حالة "جديد"، "مدفوع" أو "ملغي".
\n\n1. Value Object: Money
\n\nالمال هو Value Object كلاسيكي. كائنان Money متساويان إذا كان لهما نفس المبلغ والعملة. هما غير قابلين للتغيير (immutable).
public sealed record Money(decimal Amount, string Currency)\n{\n public static Money operator +(Money left, Money right)\n {\n if (left.Currency != right.Currency)\n throw new InvalidOperationException("Currency mismatch");\n return new Money(left.Amount + right.Amount, left.Currency);\n }\n}\n\n\nلاحظ استخدام record — في C# 9+ هذه هي الطريقة المثالية لإنشاء Value Object. فهو يوفر تلقائيًا Equals و GetHashCode والثبات.
2. Entity: Order و LineItem
\n\nللكيانات معرف. لننشئ فئة أساسية لـ Entity:
\n\npublic abstract class Entity<TId>\n{\n public TId Id { get; protected set; }\n\n protected Entity(TId id) => Id = id;\n\n public override bool Equals(object? obj) =>\n obj is Entity<TId> other && Id!.Equals(other.Id);\n\n public override int GetHashCode() => Id!.GetHashCode();\n}\n\n\nالآن Order و LineItem:
public class Order : Entity<Guid>\n{\n private readonly List<LineItem> _items = new();\n public IReadOnlyList<LineItem> Items => _items.AsReadOnly();\n public OrderStatus Status { get; private set; }\n public Money Total { get; privat