Почему простое объединение модулей не работает
Современные фронтенд‑приложения всё чаще строятся по принципу Atomic Design и Feature‑Driven Architecture. Первое разбивает UI на атомы, молекулы и организует их в каталог shared/, где находятся переиспользуемые компоненты и утилиты. Второе изолирует бизнес‑логики в отдельных доменах, каждый из которых живёт в своей папке внутри features/. На первый взгляд такой подход гарантирует чистую модульность: каждый фича‑модуль может обращаться лишь к shared/, но не знает о соседних модулях.
Однако реальное приложение — это не набор независимых островков, а система, где функции взаимодействуют. Возникает вопрос: как обеспечить связь между фичами, не разрушая их изоляцию? Ответ лежит в введении дополнительного уровня — слоя страниц (pages/), который выступает в роли Composition Root.
Структура проекта с корнем композиции
src/
├─ shared/ # переиспользуемый UI и утилиты (Atomic Design)
├─ features/ # изолированные домены (Feature‑Driven)
│ ├─ cart/
│ └─ checkout/
├─ pages/ # оркестровка и композиция компонентов
└─ app/ # глобальная инициализация
shared/остаётся единственной точкой доступа к общим ресурсам.features/сохраняет строгую границу: любой модуль внутри может импортировать только изshared/, но никогда не «видит» соседних фич.pages/собирает готовые UI‑страницы, комбинируя отдельные фичи в нужном порядке и передавая между ними данные через явно определённые контракты (props, контекст, Redux‑состояния и пр.).app/отвечает за стартовое подключение корня композиции к DOM и настройку глобального состояния.
Риск «поглощения» фич: God Feature
Если попытаться обойти слой pages/ и позволить, к примеру, features/cart напрямую импортировать features/checkout, сразу нарушается принцип изоляции. В результате один из модулей начинает «поглощать» другой, превращаясь в God Feature — монолитный блок, содержащий чужие типы, бизнес‑логики и зависимости. Последствия:
- Рост сложности – каждый модуль становится всё более громоздким, а изменения в одном месте могут неожиданно затронуть другие части системы.
- Трудности тестирования – единичные тесты ломаются из‑за скрытых зависимостей.
- Снижение переиспользуемости – модуль, который теперь знает о нескольких доменах, трудно вынести в другой проект.
- Проблемы с масштабированием – при росте команды и количества фич каждый разработчик сталкивается с конфликтами импортов и несогласованными контрактами.
Как слой страниц решает проблему
pages/ выступает в роли корня композиции, где собираются отдельные фичи в целостные пользовательские сценарии. Вместо того чтобы фичи «шептались» друг с другом, они общаются через слой страниц:
- Явные зависимости –
pages/cart-page.tsxимпортируетCartизfeatures/cartиCheckoutButtonизfeatures/checkout. При этомCartостаётся полностью независимым отCheckoutButton. - Контракты на уровне UI – передача данных реализуется через свойства компонентов или контекст, что делает взаимодействие предсказуемым и проверяемым типами.
- Локальная оркестровка – бизнес‑логика, связывающая несколько фич (например, «при клике “Оформить” в корзине открыть форму чек‑аута»), размещается в страницах, а не в самих фичах.
Таким образом, pages/ сохраняет чистоту доменных границ, а также упрощает поддержку: изменения в UI‑потоке требуют правок только в этом слое, не затрагивая внутреннюю реализацию фич.
Практические рекомендации по внедрению корня композиции
- Определите границы фич – каждый модуль в
features/должен иметь чётко ограниченный публичный API (компоненты, хуки, сервисы). - Избегайте взаимных импортов – настройте линтер (ESLint, TypeScript) с правилами
no-restricted-imports, чтобы запретить импорт изfeatures/*в другие фичи. - Соберите страницы как композиционные единицы – в
pages/создавайте отдельные файлы для каждой пользовательской истории (например,CartPage.tsx,CheckoutPage.tsx). - Используйте контекст или глобальное состояние только в
pages/– если несколько фич нуждаются в совместном состоянии, предоставьте его через контекст, объявленный в странице. - Тестируйте страницы изолированно – пишите интеграционные тесты уровня страниц, проверяя, как они собирают фичи, а юнит‑тесты оставляйте чисто внутри отдельных модулей.
Преимущества подхода
- Сохранение модульности – фичи остаются небольшими, самодостаточными и легко переиспользуемыми.
- Управляемая сложность – вся оркестровка сосредоточена в одном месте, что упрощает понимание пользовательского потока.
- Гибкость развития – новые страницы могут комбинировать уже существующие фичи без изменений их кода.
- Улучшенная тестируемость – изоляция бизнес‑логики позволяет писать чистые юнит‑тесты, а страницы покрывать интеграционными сценариями.
Внедрение корня композиции в архитектуру фронтенда — это не просто формальная перестройка каталогов, а стратегический шаг к построению масштабируемых, поддерживаемых и легко расширяемых приложений. Слой pages/ обеспечивает безопасный мост между изолированными доменами, позволяя им взаимодействовать без компромисса в отношении чистоты кода и принципов модульности.