يستخدم العديد من مطوري Node.js تبعيات الوحدة النمطية (حصريًا) باستخدام require () لربط الوحدات النمطية ، ولكن هناك طرق أخرى مع إيجابيات وسلبيات. سأتحدث عنها في هذا المقال. سيتم النظر في أربعة مناهج:
- التبعيات الصعبة (تتطلب ())
- حقن التبعية
- محدد موقع الخدمة
- حاويات التبعية المدمجة (حاوية DI)
قليلا عن وحدات
الوحدات والبنية المعيارية هي أساس Node.js. توفر الوحدات التغليف (إخفاء تفاصيل التنفيذ وفتح الواجهة فقط باستخدام module.exports) ، إعادة استخدام الرمز ، تقسيم الرمز المنطقي إلى ملفات. تتكون جميع تطبيقات Node.js تقريبًا من العديد من الوحدات التي يجب أن تتفاعل بطريقة أو بأخرى. إذا قمت بربط الوحدات بشكل غير صحيح أو تركت تفاعل الوحدات النمطية ينجرف ، يمكنك أن تجد بسرعة كبيرة أن التطبيق يبدأ في "الانهيار": التغييرات في الكود في مكان ما تؤدي إلى انهيار في مكان آخر ، ويصبح اختبار الوحدة مستحيلًا. من الناحية المثالية ، يجب أن يكون للوحدات النمطية
اتصال عالي ، ولكن
اقتران منخفض.
الإدمان الصعب
يحدث اعتماد قوي لوحدة واحدة على وحدة أخرى عند استخدام (). هذا هو نهج فعال وبسيط ومشترك. على سبيل المثال ، نريد فقط توصيل الوحدة المسؤولة عن التفاعل مع قاعدة البيانات:
الايجابيات:
- البساطة
- التنظيم المرئي للوحدات
- من السهل تصحيح الأخطاء
سلبيات:
- صعوبة إعادة استخدام الوحدة النمطية (على سبيل المثال ، إذا كنا نريد استخدام الوحدة النمطية لدينا بشكل متكرر ، ولكن مع مثيل مختلف من قاعدة البيانات)
- صعوبة اختبار الوحدة (يجب عليك إنشاء مثيل لقاعدة بيانات وهمية وتمريرها بطريقة ما إلى الوحدة النمطية)
ملخص:
هذا النهج مفيد للتطبيقات الصغيرة أو النماذج الأولية ، وكذلك لتوصيل الوحدات النمطية عديمي الجنسية: المصانع والمصممين ومجموعات الميزات.
حقن التبعية
الفكرة الرئيسية لحقن التبعية هي نقل التبعيات من مكون خارجي إلى الوحدة. وبالتالي ، يتم التخلص من التبعية الثابتة في الوحدة النمطية ويصبح من الممكن إعادة استخدامها في سياقات مختلفة (على سبيل المثال ، مع مثيلات قاعدة بيانات مختلفة).
يمكن تنفيذ حقن التبعية بتمرير التبعية في وسيطة المنشئ أو عن طريق تعيين خصائص الوحدة ، ولكن من الناحية العملية ، من الأفضل استخدام الطريقة الأولى. لنطبق تطبيق التبعيات في الممارسة العملية عن طريق إنشاء مثيل لقاعدة البيانات باستخدام المصنع ونقله إلى الوحدة النمطية لدينا:
الوحدة الخارجية:
const dbFactory = require('db'); const OurModule = require('./ourModule.js'); const dbInstance = dbFactory.createInstance('instance1'); const ourModule = OurModule(dbInstance);
لا يمكننا الآن إعادة استخدام الوحدة النمطية الخاصة بنا فحسب ، بل يمكننا أيضًا كتابة اختبار وحدة لها بسهولة: فقط قم بإنشاء كائن وهمي لمثيل قاعدة البيانات ونقله إلى الوحدة النمطية.
الايجابيات:
- سهولة اختبارات وحدة الكتابة
- زيادة قابلية إعادة استخدام الوحدات
- انخفاض المشاركة ، وزيادة الاتصال
- نقل مسؤولية إنشاء التبعيات إلى مستوى أعلى - غالبًا ما يؤدي ذلك إلى تحسين قابلية البرنامج للقراءة ، حيث يتم تجميع التبعيات المهمة في مكان واحد ، وليس الانتشار بواسطة الوحدات النمطية
سلبيات:
- الحاجة إلى تصميم تبعية أكثر شمولًا: على سبيل المثال ، يجب اتباع ترتيب معين لتهيئة الوحدة النمطية
- تعقيد إدارة التبعية ، خاصة عندما يكون هناك الكثير
- تدهور في فهم رمز الوحدة النمطية: يعد كتابة التعليمات البرمجية الوحدة عندما يأتي التبعية من الخارج أكثر صعوبة لأننا لا نستطيع النظر مباشرة في هذه التبعية.
ملخص:
يزيد حقن التبعية من تعقيد وحجم التطبيق ، ولكن في المقابل يسمح بإعادة الاستخدام ويجعل الاختبار أسهل. يجب أن يقرر المطور ما هو أكثر أهمية بالنسبة له في حالة معينة - بساطة التبعية الشديدة أو الإمكانيات الأوسع لإدخال التبعية.
محدد موقع الخدمة
الفكرة هي أن يكون لديك سجل تبعية يعمل كوسيط عند تحميل تبعية مع أي وحدة نمطية. بدلاً من الربط الثابت ، يتم طلب التبعيات بواسطة الوحدة النمطية من محدد موقع الخدمة. من الواضح ، أن الوحدات لديها تبعية جديدة - محدد موقع الخدمة نفسه. مثال على محدد موقع الخدمة هو نظام الوحدة النمطية Node.js: الوحدات النمطية طلب تبعية باستخدام require (). في المثال التالي ، سنقوم بإنشاء محدد موقع خدمة ، وتسجيل مثيلات قاعدة البيانات والوحدة الخاصة بنا.
الوحدة الخارجية:
const serviceLocator = require('./serviceLocator.js')(); serviceLocator.register('someParameter', 'someValue'); serviceLocator.factory('db', require('db')); serviceLocator.factory('ourModule', require('ourModule')); const ourModule = serviceLocator.get('ourModule');
وحدة لدينا:
تجدر الإشارة إلى أن محدد موقع الخدمة يقوم بتخزين مصانع الخدمة بدلاً من المثيلات ، وهذا أمر منطقي. حصلنا على فوائد التهيئة البطيئة ، والآن لا داعي للقلق بشأن ترتيب التهيئة للوحدات النمطية - سيتم تهيئة جميع الوحدات عند الحاجة إليها. بالإضافة إلى ذلك ، حصلنا على فرصة لتخزين المعلمات في محدد موقع الخدمة (راجع "someParameter").
الايجابيات:
- سهولة اختبارات وحدة الكتابة
- يعد إعادة استخدام الوحدة أسهل من إدمانها الصعب
- انخفاض المشاركة وزيادة الاتصال مقارنة بالإدمان الصعب
- نقل المسؤولية عن إنشاء التبعيات إلى مستوى أعلى
- لا حاجة لمتابعة ترتيب تهيئة الوحدة النمطية
سلبيات:
- يعد إعادة استخدام الوحدة النمطية أكثر صعوبة من تطبيق التبعية (بسبب التبعية الإضافية لموقع محدد الخدمة)
- قابلية القراءة: من الصعب فهم ما تقوم به خدمة التبعية المطلوبة
- زيادة المشاركة مقارنة مع حقن التبعية
ملخص
بشكل عام ، يشبه محدد موقع الخدمة حقنة التبعية ، في بعض النواحي يكون الأمر أسهل (لا يوجد ترتيب تهيئة) ، وفي بعض الحالات يكون الأمر أكثر صعوبة (أقل من إمكانية إعادة استخدام الكود).
حاويات التبعية المدمجة (حاوية DI)
يحتوي محدد موقع الخدمة على عيب بسبب نادراً ما يتم تطبيقه في الممارسة - اعتماد الوحدات على محدد الموقع نفسه. لا تحتوي حاويات التبعية المضمّنة (حاويات DI) على هذا العيب. في الواقع ، هذا هو نفس محدد موقع الخدمة الذي يحتوي على وظيفة إضافية تحدد تبعيات الوحدة قبل إنشاء مثيلها. يمكنك تحديد تبعيات الوحدة النمطية عن طريق تحليل واستخراج الوسائط من مُنشئ الوحدة النمطية (في JavaScript ، يمكنك إرسال رابط إلى دالة إلى سلسلة باستخدام toString ()). هذه الطريقة مناسبة إذا تم تطوير الخادم بشكل خالص. إذا تم كتابة رمز العميل ، فغالبًا ما يتم تصغيره وسيكون من غير المجدي استخراج أسماء الوسائط. في هذه الحالة ، يمكن تمرير قائمة التبعيات كصفيف من السلاسل (في Angular.js ، بناءً على استخدام حاويات DI ، يتم استخدام هذا النهج). نقوم بتطبيق حاوية DI باستخدام تحليل وسيطات المُنشئ:
const fnArgs = require('parse-fn-args'); module.exports = function() { const dependencies = {}; const factories = {}; const diContainer = {}; diContainer.factory = (name, factory) => { factories[name] = factory; }; diContainer.register = (name, dep) => { dependencies[name] = dep; }; diContainer.get = (name) => { if(!dependencies[name]) { const factory = factories[name]; dependencies[name] = factory && diContainer.inject(factory); if(!dependencies[name]) { throw new Error('Cannot find module: ' + name); } } diContainer.inject = (factory) => { const args = fnArgs(factory) .map(dependency => diContainer.get(dependency)); return factory.apply(null, args); } return dependencies[name]; };
بالمقارنة مع محدد موقع الخدمة ، تمت إضافة طريقة الحقن ، والتي تحدد تبعيات الوحدة قبل إنشاء مثيلها. لم يتغير رمز الوحدة الخارجية كثيرًا:
const diContainer = require('./diContainer.js')(); diContainer.register('someParameter', 'someValue'); diContainer.factory('db', require('db')); diContainer.factory('ourModule', require('ourModule')); const ourModule = diContainer.get('ourModule');
تبدو الوحدة النمطية لدينا تمامًا كما هو الحال مع حقن التبعية البسيطة:
الآن يمكن استدعاء الوحدة النمطية الخاصة بنا على حد سواء بمساعدة حاوية DI وتمريرها مباشرة حالات التبعية اللازمة ، وذلك باستخدام حقنة تبعية بسيطة.
الايجابيات:
- سهولة اختبارات وحدة الكتابة
- من السهل إعادة استخدام الوحدات
- تقليل المشاركة وزيادة الاتصال بالوحدات النمطية (خاصة بالمقارنة مع محدد موقع الخدمة)
- نقل المسؤولية عن إنشاء التبعيات إلى مستوى أعلى
- لا حاجة لتتبع تهيئة الوحدة
أكبر ناقص:
- مضاعفات كبيرة من منطق وحدة ملزمة
ملخص
هذا النهج أكثر صعوبة لفهم ويحتوي على رمز أكثر قليلاً ، ولكن الأمر يستحق الوقت الذي تستغرقه بسبب قوته والأناقة. في المشروعات الصغيرة ، قد يكون هذا النهج زائداً عن الحاجة ، ولكن يجب التفكير فيه في حالة تصميم تطبيق كبير.
الخاتمة
تم النظر في الأساليب الأساسية لوحدة الربط في Node.js. كما يحدث عادة ، "النقطة الفضية" غير موجودة ، ولكن يجب أن يكون المطور على دراية بالبدائل المحتملة واختيار الحل الأنسب لكل حالة محددة.
تستند المقالة إلى فصل من كتاب
Node.js Design Patterns الذي صدر في عام 2017. لسوء الحظ ، هناك العديد من الأشياء في الكتاب قديمة بالفعل ، لذا لا يمكنني أن أوصي بنسبة 100٪ بقراءتها ، لكن بعض الأشياء لا تزال صالحة حتى اليوم.