Что такое Low Coupling (слабая связанность)?

«Что такое Low Coupling (слабая связанность)?» — вопрос из категории Архитектура, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Low Coupling — это принцип проектирования, означающий, что модули или классы в системе должны иметь минимальные зависимости друг от друге. В Node.js-экосистеме это критически важно для создания поддерживаемых, тестируемых и масштабируемых приложений, особенно в контексте микросервисов.

Проблема сильной связанности (High Coupling) в Node.js:

// Плохо: Класс Order жёстко зависит от конкретной реализации базы данных и логгера.
const { MongoClient } = require('mongodb');
const winston = require('winston');

class OrderService {
  constructor() {
    this.client = new MongoClient('mongodb://localhost:27017'); // Прямое создание
    this.logger = winston.createLogger({ /* конфиг */ }); // Прямое создание
  }

  async createOrder(data) {
    await this.client.connect();
    // ... логика
    this.logger.info('Order created'); // Прямой вызов
  }
}

// Тестировать такой класс очень сложно. Мы не можем подменить БД или логгер.

Решение: слабая связанность через Dependency Injection (DI):

// Хорошо: Зависимости передаются извне.
class OrderService {
  constructor(dbClient, logger) { // Принимаем абстракции
    this.dbClient = dbClient;
    this.logger = logger;
  }

  async createOrder(data) {
    await this.dbClient.connect();
    // ... логика
    this.logger.info('Order created');
  }
}

// Где-то в точке входа приложения (app.js или контейнере DI) собираем зависимости:
const { MongoClient } = require('mongodb');
const winston = require('winston');

const dbClient = new MongoClient('mongodb://localhost:27017');
const logger = winston.createLogger({});

const orderService = new OrderService(dbClient, logger); // Внедрение зависимостей

// Теперь для модульного теста мы можем легко передать моки:
const mockDbClient = { connect: jest.fn() };
const mockLogger = { info: jest.fn() };
const testOrderService = new OrderService(mockDbClient, mockLogger);
// Легко тестируем, был ли вызван logger.info с нужными параметрами.

Как достичь слабой связанности в Node.js:

  1. Использовать интерфейсы (абстракции): Программировать на основе контрактов (например, что класс Logger должен иметь метод info(message)), а не конкретных реализаций.
  2. Dependency Injection (DI): Передавать зависимости как параметры конструктора или функций. Можно использовать DI-контейнеры вроде awilix или tsyringe для больших проектов.
  3. Использовать Event Emitter: Вместо прямых вызовов модули могут общаться через события (EventEmitter), что уменьшает прямую связь.
  4. Следовать принципу инверсии зависимостей (DIP): Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

Практическая польза:

  • Упрощение тестирования: Моки и стабы внедряются без изменения кода тестируемого модуля.
  • Гибкость: Замена библиотеки для работы с БД (с Mongoose на Prisma) затрагивает только слой инициализации зависимостей.
  • Переиспользование: Модуль с низкой связностью легче использовать в другом проекте.