Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи? Написать
Дайджесты новостей
Архитектура модульного фронтенда маркетплейса: feature-модули и DI-контейнер

Модульный фронтенд маркетплейса: как распилить монолит на feature-модули и связать их через DI

Когда над фронтендом крупного e-commerce приложения работают десятки команд, классическая организация проекта по техническим слоям перестает справляться. Общие папки components/, services/ и hooks/ удобны на старте, но в масштабе маркетплейса быстро превращаются в распутанный монолит. Изменение логики скидок в корзине случайно ломает каталог, а сквозные циклические импорты делают рефакторинг рискованным.

Команда фронтенд-инфраструктуры маркетплейса RWB применила архитектурный подход: сохранение единого модульного монолита с декомпозицией кодовой базы на автономные feature-модули, связываемые через легковесный DI-контейнер (Dependency Injection). В отличие от микрофронтендов, этот подход сохраняет единый цикл сборки и общую дизайн-систему, обеспечивая командам четкие границы ответственности.

Пределы технических слоев и выбор между FSD и DI

Традиционная структура группирует файлы по их роли во фреймворке: компоненты лежат в одной папке, запросы к API — во второй, утилиты — в третьей. Разработчик, реализующий оформление заказа (checkout), вынужден постоянно переключаться между разными концами проекта. Контекст предметной области размывается, а связи между экранами становятся неконтролируемыми.

Методология Feature-Sliced Design (FSD) решает эту проблему через жесткую слоистую иерархию (app, pages, widgets, features, entities, shared). Однако в реальных масштабных маркетплейсах строгие рамки FSD иногда порождают бюрократию: соседние доменные сущности требуют перекрестного взаимодействия, а вынесение логики вниз раздувает слой shared.

Подход на основе feature-модулей с DI предлагает гибкий компромисс:

Архитектурный подходПорог входаИзоляция доменовМасштабируемость командКонтроль связности
Технические слоиНизкийСлабаяДо 5–10 инженеровОтсутствует
Feature-Sliced DesignСреднийСтрогая иерархическаяДесятки командПравила слоев
Feature-модули + DIСреднийМодульная контрактнаяДесятки командИнтерфейсы и DI

В центре архитектуры RWB лежит вертикальное разделение приложения на функциональные бизнес-модули: catalog, cart, delivery, checkout. Каждый модуль представляет собой замкнутую подсистему для конкретного пользовательского сценария.

Роль Dependency Injection и точка сборки приложения

Внедрение зависимостей (DI) традиционно считается серверным паттерном, однако в интерфейсе оно выполняет важную функцию: отделяет правила бизнеса от жизненного цикла компонентов React.

Без DI модуль оформления заказа напрямую импортировал бы реализацию доставки: import { DeliveryService } from "@/features/delivery". В этом случае checkout жестко привязывается к конкретному сервису доставки и его сетевым клиентам. Протестировать логику оформления заказа без поднятия всего окружения доставки становится невозможно.

При использовании DI модуль объявляет только интерфейс потребности (consumer-owned contract):

export interface DeliveryCalculator {
  calculateCost(items: OrderItem[]): Promise<Money>;
}

export class CheckoutService {
  constructor(private deliveryCalc: DeliveryCalculator) {}
}

Конкретный сервис передается в конструктор снаружи. Местом, где интерфейсы соединяются с реализациями, выступает точка сборки — composition root на уровне приложения (app). DI-контейнер автоматизирует регистрацию токенов и фабрик, избавляя инженеров от ручной связки объектов.

Анатомия образцового feature-модуля

Каждый модуль внутри кодовой базы разграничивает роли внутренних слоев:

  • api/: клиенты сетевых запросов, DTO и функции преобразования данных бэкенда в доменные модели.
  • model/: сущности предметной области, чистые функции расчета, валидаторы и интерфейсы сервисов.
  • services/: классы бизнес-логики, реализующие пользовательские сценарии и получающие зависимости через конструктор.
  • ui/: визуальные компоненты React, отвечающие за отрисовку интерфейса.
  • hooks/: реактивные адаптеры, связывающие вызовы сервисов с состоянием компонентов и пользовательским вводом.
  • index.ts: публичный фасад (Public API) модуля.

Фасад выполняет роль единственного шлюза во внешний мир. Через него экспортируются только интерфейсы сервисов, готовые визуальные виджеты и фабрики. Прямой импорт внутренних файлов модуля в обход фасада запрещен: если внешнему коду требуется внутренняя функция, ее выносят в публичный контракт осознанно.

Управление состоянием и изоляция платформенных API

Распространенная ошибка — попытка сложить все данные приложения в единое глобальное хранилище. В подходе RWB состояние разделено по природе данных:

  1. Серверное состояние (Server Cache): списки товаров, параметры заказов и каталог кэшируются через специализированные клиенты (React Query или RTK Query).
  2. Клиентское состояние фичи (Feature State): данные, нужные нескольким компонентам модуля (например, черновик заказа), инкапсулируются внутри модуля через легковесные хранилища (Zustand или Context).
  3. Локальное состояние UI: статус открытия меню или фокус инпута остаются внутри стандартных хуков useState.

Не менее строгая граница возводится вокруг браузерных API. Прямое обращение к window, localStorage, cookies или navigator из бизнес-логики запрещено. Доступ изолируется в адаптерах слоя shared/platform. Адаптер обрабатывает ошибки переполнения квоты и позволяет выполнять код на сервере (Server-Side Rendering, SSR) без риска падения из-за отсутствия глобального объекта window.

Контроль границ через ESLint и готовность к ИИ-ассистентам

Архитектурные соглашения в RWB контролируются статическим анализатором ESLint на уровне CI-пайплайна:

  • Запрет глубоких импортов: блокируются попытки импорта из внутренних папок чужих модулей.
  • Запрет циклических перекрестных зависимостей: модули одного уровня не импортируют исполняемый код друг друга напрямую.
  • Изоляция окружения: запрещено прямое обращение к глобальным переменным браузера вне платформенных адаптеров.

Дополнительный эффект модульности — готовность кодовой базы к работе с ИИ-ассистентами (LLM-ready codebase). Изолированные модули с явными контрактами index.ts и записями архитектурных решений (ADR) сокращают контекстное окно для нейросети. Ассистент генерирует код в границах одного модуля, не галлюцинируя скрытыми зависимостями, а линтер мгновенно отсекает некорректные импорты.

План постепенной миграции и критерии готовности

Переход на модульную архитектуру не требует остановки продуктовой разработки:

  1. Выделение первой изолированной фичи: выбирается модуль с минимальным числом связей (например, избранное или профиль).
  2. Формирование публичного контракта: создается файл index.ts с экспортом только необходимых типов и компонентов.
  3. Подключение правила линтера: в CI активируется запрет глубоких импортов для переработанного модуля.
  4. Вынесение зависимостей в composition root: создание сервисов переносится на уровень приложения, а модуль начинает принимать зависимости через интерфейсы.
  5. Покрытие тестами: пишутся юнит-тесты на чистую логику сервисов без jsdom и интеграционный тест сборки контейнера зависимостей.

Модульная модель позволяет маркетплейсу развивать сложные сценарии силами распределенных команд, сохраняя предсказуемость кодовой базы и скорость поставки изменений.