حقن التبعية في JavaScript: دليل عملي لـ Node.js

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: مشكلة الترابط الصلب للكود

\n\n

كل مطور Node.js يواجه عاجلاً أم آجلاً موقفًا يؤدي فيه تغيير في وحدة واحدة إلى سلسلة من التعديلات في عشر وحدات أخرى. يتجلى هذا بشكل خاص عند كتابة اختبارات الوحدة: لاختبار خدمة واحدة، يجب تشغيل قاعدة البيانات وواجهات API الخارجية وذاكرة التخزين المؤقت. جذر المشكلة هو الترابط الصلب (tight coupling).

\n\n

حقن التبعية (DI) هو نمط تصميم يحل هذه المشكلة. بدلاً من أن يقوم الفصل أو الدالة بإنشاء تبعياته بنفسه، فإنه يتلقاها من الخارج. هذا يجعل الكود مرنًا وقابلًا للاختبار ومتوافقًا مع مبدأ عكس التبعيات (Dependency Inversion Principle) من SOLID.

\n\n

في هذه المقالة، سنستعرض DI عمليًا: من الحقن اليدوي البسيط إلى استخدام الحاويات الاحترافية في Node.js.

\n\n

1. الحقن اليدوي للتبعيات: الخطوة الأولى

\n\n

النهج السيء: الترابط الصلب

\n

لننظر إلى مثال نموذجي لخدمة إرسال البريد الإلكتروني:

\n\n
// userService.js\nconst emailService = require('./emailService');\n\nclass UserService {\n  async registerUser(email) {\n    // اعتماد صلب على تنفيذ محدد\n    await emailService.sendWelcomeEmail(email);\n    console.log('تم تسجيل المستخدم');\n  }\n}\n\nmodule.exports = UserService;
\n\n

المشكلة: عند اختبار UserService، لا يمكن استبدال emailService بنموذج وهمي (mock). سيتعين استخدام أدوات معقدة مثل proxyquire أو jest.mock.

\n\n

النهج الجيد: الحقن عبر المُنشئ

\n

لنعد كتابة الخدمة، ونمرر التبعية عبر المُنشئ:

\n\n
// userService.js\nclass UserService {\n  constructor(emailService) {\n    this.emailService = emailService;\n  }\n\n  async registerUser(email) {\n    await this.emailService.sendWelcomeEmail(email);\n    console.log('تم تسجيل المستخدم');\n  }\n}\n\nmodule.exports = UserService;
\n\n

الآن يمكننا اختبار الخدمة بسهولة:

\n\n
// userService.test.js\nconst UserService = require('./userService');\n\nconst mockEmailService = {\n  sendWelcomeEmail: jest.fn().mockResolvedValue(true)\n};\n\ntest('يجب إرسال بريد ترحيبي', async () => {\n  const service = new UserService(mockEmailService);\n  await service.registerUser('test@test.com');\n  expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith('test@test.com');\n});
\n\n

2. أنواع حقن التبعية في JavaScript

\n\n

في JavaScript/Node.js، هناك ثلاث طرق رئيسية لحقن التبعيات:

\n\n

2.1. الحقن عبر المُنشئ

\n

الطريقة الأكثر شيوعًا وقابلية للتنبؤ. يتم تمرير التبعيات عند إنشاء الكائن:

\n\n
class DatabaseService {\n  constructor(connection) {\n    this.connection = connection;\n  }\n\n  async query(sql) {\n    return this.connection.execute(sql);\n  }\n}\n\n// الاستخدام\nconst db = new DatabaseService(new MySQLConnection());
\n\n

2.2. الحقن عبر الدوال الضابطة (Setters)

\n

مناسب عندما يمكن تغيير التبعيات أثناء عمل الكائن:

\n\n
class LoggerService {\n  setLogger(logger) {\n    this.logger = logger;\n  }\n\n  log(message) {\n    this.logger.write(`[${new Date().toISOString()}] ${message}`);\n  }\n}\n\nconst logger = new LoggerService();\nlogger.setLogger(new ConsoleLogger());\nlogger.log('تم تشغيل التطبيق');
\n\n

2.3. الحقن عبر معاملات الدالة

\n

مثالي للنهج الوظيفي والأدوات المساعدة:

\n\n
function sendNotification(user, emailService, smsService) {\n  emailService.send(user.email, 'إشعار');\n  smsService.send(user.phone, 'إشعار');\n}\n\n// الاختبار\nconst mockEmail = { send: jest.fn() };\nconst mockSms = { send: jest.fn() };\nsendNotification({ email: 'a@b.com', phone: '123' }, mockEmail, mockSms);
\n\n

3.

المدونات

توصيات الكتب