← Назад к вопросам

Как снизить зацепление (coupling) кода в приложениях на Node.js?

2.8 Senior🔥 202 комментариев
#Soft skills и опыт работы#Архитектура и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как снизить зацепление (coupling) кода в приложениях на Node.js?

Зацепление (coupling) — это степень зависимости между модулями. Высокое зацепление приводит к сложности изменений, низкой переиспользуемости и затруднённому тестированию. Вот проверенные техники для снижения coupling в Node.js.

1. Dependency Injection (Инъекция зависимостей)

Вместо создания зависимостей внутри класса, передавай их через конструктор:

class UserService {
  constructor(db, logger, emailService) {
    this.db = db;
    this.logger = logger;
    this.emailService = emailService;
  }

  createUser(data) {
    this.db.insert('users', data);
    this.logger.info('User created');
    this.emailService.send(data.email, 'Welcome!');
  }
}

const db = new Database();
const logger = new Logger();
const emailService = new EmailService();
const userService = new UserService(db, logger, emailService);

2. Dependency Injection Container (IoC контейнер)

const awilix = require('awilix');

const container = awilix.createContainer();

container.register({
  db: awilix.asClass(Database).singleton(),
  logger: awilix.asClass(Logger).singleton(),
  emailService: awilix.asClass(EmailService).singleton(),
  userService: awilix.asClass(UserService).singleton(),
});

const userService = container.resolve('userService');
userService.createUser({ name: 'John' });

3. Использование интерфейсов и абстракций

class DatabaseInterface {
  async insert(table, data) { throw new Error('Not implemented'); }
  async find(table, query) { throw new Error('Not implemented'); }
}

class PostgresDatabase extends DatabaseInterface {
  async insert(table, data) {
    console.log(`Inserting into ${table}:`, data);
  }
  async find(table, query) {
    console.log(`Finding in ${table}:`, query);
  }
}

class UserService {
  constructor(db) {
    this.db = db;
  }

  async createUser(data) {
    await this.db.insert('users', data);
  }
}

const postgres = new PostgresDatabase();
const userService = new UserService(postgres);

4. Event Emitters вместо прямых вызовов

const EventEmitter = require('events');

class UserService extends EventEmitter {
  createUser(data) {
    const user = this.createUserInDB(data);
    this.emit('user:created', user);
  }
}

const userService = new UserService();

userService.on('user:created', (user) => {
  emailService.sendWelcomeEmail(user);
});

userService.on('user:created', (user) => {
  notificationService.notifyAdmins(user);
});

userService.createUser({ name: 'John' });

5. Repository Pattern (Паттерн Хранилище)

class UserRepository {
  constructor(db) {
    this.db = db;
  }

  async findById(id) {
    return this.db.user.findUnique({ where: { id } });
  }

  async save(user) {
    return this.db.user.create({ data: user });
  }
}

class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async getUser(id) {
    return this.userRepository.findById(id);
  }

  async createUser(data) {
    return this.userRepository.save(data);
  }
}

const db = new PrismaClient();
const userRepository = new UserRepository(db);
const userService = new UserService(userRepository);

6. Разделение на слои (Layered Architecture)

Организуй код по слоям:

  • presentation/ (controllers, routes)
  • application/ (use-cases, dtos)
  • domain/ (entities, repositories interfaces)
  • infrastructure/ (repositories, db, services)

7. Модульная структура проекта

src/
├── modules/
│   ├── user/
│   │   ├── User.js
│   │   ├── UserService.js
│   │   ├── UserRepository.js
│   │   ├── UserController.js
│   │   └── userRoutes.js
│   ├── post/
│   └── comment/

8. Middleware и Pipeline вместо прямых вызовов

const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'Unauthorized' });
  
  const user = verifyToken(token);
  if (!user) return res.status(401).json({ error: 'Invalid token' });
  
  req.user = user;
  next();
};

app.get('/users/:id', authMiddleware, (req, res) => {
  const userData = getUserFromDB(req.params.id);
  res.json(userData);
});

9. Полный пример рефакторинга

class OrderService {
  constructor(orderRepository, emailService, paymentGateway) {
    this.orderRepository = orderRepository;
    this.emailService = emailService;
    this.paymentGateway = paymentGateway;
  }

  async createOrder(orderData) {
    const order = await this.orderRepository.save(orderData);
    
    await Promise.all([
      this.emailService.sendOrderConfirmation(order),
      this.paymentGateway.charge(order.amount),
    ]);
    
    return order;
  }
}

const container = awilix.createContainer();
container.register({
  orderRepository: awilix.asClass(OrderRepository).singleton(),
  emailService: awilix.asClass(EmailService).singleton(),
  paymentGateway: awilix.asClass(PaymentGateway).singleton(),
  orderService: awilix.asClass(OrderService).singleton(),
});

Чеклист снижения coupling

  • Используй Dependency Injection вместо прямого создания объектов
  • Зависи от интерфейсов, а не от конкретных реализаций
  • Используй EventEmitter для асинхронной коммуникации
  • Применяй Repository Pattern для доступа к данным
  • Разделяй код на слои
  • Группируй код по функциональности, а не по типам
  • Используй middleware для кросс-функциональной логики
  • Регулярно рефакторь код

Снижение coupling требует дополнительных усилий на начальном этапе, но значительно облегчает поддержку, тестирование и эволюцию приложения.