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

Сталкивался ли с циклической зависимостью

2.0 Middle🔥 141 комментариев
#Node.js и JavaScript#Архитектура и паттерны

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

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

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

Циклические зависимости в Node.js и как их решать

Да, циклические зависимости — частая проблема в разработке. Встречал их не раз, особенно в растущих проектах. Это когда модуль A зависит от B, а B зависит от A, что создает бесконечный цикл загрузки.

Типичный пример проблемы

// users.js
const { getOrders } = require("./orders");

function getUser(id) {
  return { id, orders: getOrders(id) };
}

module.exports = { getUser };

// orders.js
const { getUser } = require("./users");

function getOrders(userId) {
  const user = getUser(userId);
  return user.orders || [];
}

module.exports = { getOrders };

При загрузке этих модулей произойдет ошибка, так как каждый ждет инициализации другого.

Решение 1: Ленивая загрузка (Most Common)

Загружаю модуль внутри функции, а не в начале файла:

// users.js
function getUser(id) {
  const { getOrders } = require("./orders");
  return { id, orders: getOrders(id) };
}

module.exports = { getUser };

// orders.js
function getOrders(userId) {
  const { getUser } = require("./users");
  const user = getUser(userId);
  return user.orders || [];
}

module.exports = { getOrders };

Работает потому что require выполняется только когда функция вызывается, а не когда модуль загружается.

Решение 2: Рефакторинг с третьим модулем

Вынес общую логику в отдельный модуль:

// userOrders.js — новый модуль
function getUserWithOrders(userId, getOrdersFn) {
  return { userId, orders: getOrdersFn(userId) };
}

module.exports = { getUserWithOrders };

// users.js
const { getUserWithOrders } = require("./userOrders");
const { getOrders } = require("./orders");

function getUser(id) {
  return getUserWithOrders(id, getOrders);
}

module.exports = { getUser };

// orders.js
function getOrders(userId) {
  // независимо работает
  return [{ id: 1, amount: 100 }];
}

module.exports = { getOrders };

Решение 3: Инъекция зависимостей

В больших проектах использую DI контейнер (например, InversifyJS или TypeDI в NestJS):

// container.ts
import { Container } from "inversify";
import { UserService } from "./users";
import { OrderService } from "./orders";

const container = new Container();
container.bind(UserService).toSelf();
container.bind(OrderService).toSelf();

export { container };

// users.ts
import { injectable, inject } from "inversify";

@injectable()
export class UserService {
  constructor(@inject(OrderService) private orderService: OrderService) {}
  
  getUser(id: string) {
    return { id, orders: this.orderService.getOrders(id) };
  }
}

// orders.ts — не зависит от UserService
@injectable()
export class OrderService {
  getOrders(userId: string) {
    return [{ id: 1 }];
  }
}

Решение 4: Правильная архитектура

Проектирую с самого начала, чтобы избежать циклов:

  • Layered Architecture: domain → application → infrastructure (зависимости только внутрь)
  • Interfaces/Contracts: зависимость от интерфейса, не от реализации
  • Event-Driven: вместо прямых вызовов использую события/pub-sub

Как я диагностирую циклические зависимости

// npm install depcheck
// depcheck найдет циклические зависимости автоматически

Когда встречал на практике

В реальном проекте: у нас были UserService и NotificationService. UserService отправлял уведомления при создании пользователя, а NotificationService нужен был получить информацию о пользователе. Решил через event emitter — UserService эмитит событие, NotificationService слушает независимо.

Циклические зависимости — сигнал, что нужна архитектурная переработка, а не просто хак с ленивой загрузкой.

Сталкивался ли с циклической зависимостью | PrepBro