لماذا DDD ليس مجرد "نمط آخر"
\n\nهل سبق لك أن أعدت كتابة مشروع من الصفر فقط لأن "منطق الأعمال توزع عبر جميع المتحكمات"؟ أو ربما أصبح UserManager الخاص بك يزيد عن 2000 سطر، ولا أحد يفهم ما يفعله حقًا؟ مألوف؟
في عالم تطوير Swift، اعتدنا على MVC أو VIPER أو MVVM. لكن كل هذه الأنماط تحل نفس المشكلة: كيفية فصل واجهة المستخدم عن المنطق. ومع ذلك، فهي لا تقول شيئًا تقريبًا عن كيفية تنظيم منطق الأعمال نفسه. وهنا يأتي دور التصميم القائم على المجال (DDD) — وهو نهج يضع في المركز ليس الإطار، بل مجال التطبيق الخاص بك.
\n\nفي هذه المقالة، سنشرح كيفية تطبيق التصميم القائم على المجال في Swift بأمثلة حقيقية. سترى أن DDD ليس نظرية مجردة، بل أداة قوية تجعل الكود قابلاً للقراءة والاختبار ومقاومًا للتغييرات.
\n\nما هو DDD بكلمات بسيطة
\n\nتخيل أنك تكتب تطبيقًا لمتجر إلكتروني. MVC سيقول لك: "اصنع نموذج Product، ومتحكم ProductViewController وواجهات". بينما DDD سيسألك: "ما هو 'المنتج' في عملك؟ هل يمكن أن يكون بدون سعر؟ ماذا يحدث عندما يُضاف إلى سلة التسوق؟"
التصميم القائم على المجال هو طريقة لنمذجة البرمجيات حيث يعكس الكود بدقة لغة وقواعد العمل. بدلاً من الكيانات "التقنية" (مثل DataManager)، تعمل مع كائنات المجال: Order، Customer، Invoice.
المكونات الأساسية لـ DDD
\n\nلكي لا تضيع في المصطلحات، تذكر ثلاثة مفاهيم رئيسية:
\n\n- \n
- Entity (الكيان) — كائن له هوية. كائنان بنفس الحقول هما كيانان مختلفان (مثل شخصين مختلفين بنفس الاسم). \n
- Value Object (كائن القيمة) — كائن يُعرف فقط بخصائصه. إذا تطابقت جميع حقول كائنين، فهما نفس الكائن (مثل تاريخ أو مبلغ مالي). \n
- Aggregate (التجميع) — مجموعة من الكائنات المرتبطة التي تُعتبر وحدة واحدة. للتجميع جذر (root entity) يتم من خلاله الوصول إلى جميع الكائنات الداخلية. \n
يبدو جافًا؟ لننتقل إلى Swift.
\n\nكتابة نموذج المجال في Swift
\n\nلنفترض أننا نصنع تطبيقًا لإدارة المهام (قائمة مهام). لنبدأ بأبسط شيء — Value Object لحالة المهمة.
\n\n// Value Object: حالة المهمة\nstruct TaskStatus: Equatable {\n let rawValue: String\n \n static let todo = TaskStatus(rawValue: "todo")\n static let inProgress = TaskStatus(rawValue: "inProgress")\n static let done = TaskStatus(rawValue: "done")\n \n // قاعدة عمل: يمكن الانتقال فقط إلى الحالة التالية\n func canTransition(to newStatus: TaskStatus) -> Bool {\n switch (self, newStatus) {\n case (.todo, .inProgress), (.inProgress, .done):\n return true\n default:\n return false\n }\n }\n}\n\nلاحظ: نحن لا نستخدم enum، لأنه في العمل الحقيقي قد تأتي الحالات من الخادم أو تُخزن في قاعدة البيانات. TaskStatus هو كائن غير قابل للتغيير يعرف قواعده بنفسه.
الآن لننشئ Entity — المهمة نفسها:
\n\n// Entity: المهمة\nfinal class Task {\n let id: UUID\n var title: String\n var description: String\n private(set) var status: TaskStatus\n let createdAt: Date\n \n init(id: UUID = UUID(), title: String, description: String, status: TaskStatus = .todo) {\n self.id = id\n self.title = title\n self.description = description\n self.status = status\n