هل يتحول كود JavaScript الخاص بك إلى معكرونة؟ حان وقت تطبيق DDD
\n\nهل سبق لك أن وجدت نفسك تفكر أن مشروع JavaScript أصبح يشبه طبق المعكرونة؟ الطرق تتشابك مع بعضها، التغييرات في وحدة واحدة تكسر ثلاث وحدات أخرى، ومنطق الأعمال موزع عبر المتحكمات والخدمات والأدوات المساعدة. هل هذا مألوف؟ إذا كان مشروعك قد تجاوز مرحلة "مجرد نموذج أولي"، فقد حان الوقت للتعرف على التصميم القائم على المجال (DDD).
\n\nيعتقد الكثيرون أن DDD هو حكر على المشاريع الضخمة بلغة Java أو C#. لكن هذه خرافة. التصميم القائم على المجال في مشاريع JavaScript ليس ممكنًا فحسب، بل إنه مفيد بشكل لا يصدق. فهو يساعد المطورين والأعمال على التحدث بلغة واحدة، ويجعل الكود قابلاً للتنبؤ ومقاومًا للتغييرات. في هذه المقالة، سنشرح DDD عمليًا، مع أمثلة لكود حقيقي، لتتمكن من تطبيقه اليوم.
\n\nما هو DDD ولماذا يحتاجه مطور JavaScript؟
\n\nDDD ليس مكتبة ولا إطار عمل. إنه نهج لتصميم البرمجيات يضع في المركز المجال (Domain) للأعمال. بدلاً من التفكير في جداول قاعدة البيانات أو REST API، تفكر في كيانات الأعمال: "طلب"، "عميل"، "فاتورة"، "قاعدة حساب الخصم".
\n\nالألم الرئيسي الذي يعالجه DDD هو التعقيد. عندما يعيش منطق الأعمال في مكان واحد والتفاصيل التقنية في مكان آخر، يصبح المشروع هشًا. يقدم DDD بنية واضحة تفصل القش عن الحبوب.
\n\nبالنسبة لـ JavaScript، حيث نستخدم غالبًا الكتابة الديناميكية والنماذج الأولية، يمنح DDD الانضباط. فهو يفرض وصف أنواع البيانات وقواعد تغييرها بشكل صريح، وهو أمر بالغ الأهمية للتطبيقات القابلة للتوسع على Node.js أو تطبيقات العميل المعقدة.
\n\nاللبنات الأساسية لـ DDD (مع أمثلة بلغة JS)
\n\nدعنا ننتقل إلى الجزء الأكثر إثارة للاهتمام - الكود. سنكتب نظامًا بسيطًا ولكنه معبر لإدارة الطلبات في متجر إلكتروني. سنستخدم JavaScript الحديث (ES6+).
\n\n1. كائن القيمة (Value Object)
\n\nهذا كائن غير قابل للتغيير يتم تعريفه بخصائصه، وليس بهويته. كائنا قيمة متساويان إذا كانت لهما نفس القيم. المثال المثالي هو المال والعنوان والبريد الإلكتروني.
\n\n// value-objects/Money.js\nclass Money {\n constructor(amount, currency) {\n // التحقق - جزء أساسي من DDD\n if (typeof amount !== 'number' || amount < 0) {\n throw new Error('يجب أن يكون المبلغ رقمًا موجبًا');\n }\n if (!['USD', 'RUB', 'EUR'].includes(currency)) {\n throw new Error('عملة غير مدعومة');\n }\n\n this._amount = amount;\n this._currency = currency;\n // تجميد الكائن لضمان عدم قابليته للتغيير\n Object.freeze(this);\n }\n\n get amount() { return this._amount; }\n get currency() { return this._currency; }\n\n // طريقة الأعمال: جمع الأموال\n add(otherMoney) {\n if (this.currency !== otherMoney.currency) {\n throw new Error('لا يمكن جمع عملات مختلفة');\n }\n return new Money(this.amount + otherMoney.amount, this.currency);\n }\n\n equals(other) {\n return this.amount === other.amount && this.currency === other.currency;\n }\n}\n\n// الاستخدام\nconst price = new Money(100, 'USD');\nconst tax = new Money(20, 'USD');\nconst total = price.add(tax); // Money { _amount: 120, _currency: 'USD' }\nconsole.log(total);\n\nلماذا هذا رائع؟ الآن مبلغ المال ليس مجرد رقم، بل هو كيان كامل بقواعد. لن تتمكن عن طريق الخطأ من جمع الدولارات مع الروبلات أو جعل المبلغ سالبًا.
\n\n2. الكيان (Entity)
\n\nعلى عكس كائن القيمة، الكيان له هوية. يمكن أن يتغير بمرور الوقت، لكن معرفه (ID) يبقى ثابتًا. على سبيل المثال، مستخدم أو طلب.
\n\n// entities/Order.js\nconst { v4: uuidv4 } = require('uuid'); // أو crypto.randomUUID()\n\nclass Order {\n constructor(id