Beta Документация для beta‑теста, возможны ошибки и неточности.
Перейти к содержимому

Где хранить…?

Часто при использовании какой-то структуры возникают закономерные вопросы: «Где хранить что-то?». Эта статья отвечает на некоторые из них. Большая часть ответов является продолжением логики, заложенной в FEOD, и должна быть интуитивно понятна. Поэтому не думайте, что вам нужно заучивать, где что и как должно храниться. Старайтесь сосредоточиться на понимании логики и правил FEOD, а не зазубривать ответы; статья будет мягко наводить и напоминать эту мысль.

Вполне возможно, тут не будет ответа на ваш конкретный случай — тогда постарайтесь представить, где в соответствии с логикой FEOD это должно находиться. Или ситуация, когда вы не согласны с расположением в предложенном ответе и считаете, что ваш вариант логичнее: не проблема. Пока вы руководствуетесь логикой FEOD и правилами импорта, вы будете правы.

Конечно, тут есть исключительные ситуации, которые противоречат строгим правилам, однако ещё одна важная мысль: лучшее — враг хорошего. Если ради соблюдения правил вы потеряете что-то важное, возможно, стоит отступить от правила. В этом случае задокументируйте это и объясните, почему вы поступили именно так. Это поможет вам и другим разработчикам в будущем понять ход ваших мыслей.

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

... данные для i18n?

в app:

Логика app: тут хранятся конфигурации и логика для запуска проекта. Поэтому если речь о глобальном конфиге со всеми переводами без привязки к конкретным модулям или страницам, то они должны находиться в app. Либо, если вы вынуждены их хранить единым конфигом, то также в app.

в modules:

Логика modules: тут хранятся изолированные модули с логикой и связанные с ними сущности. Поэтому если речь о переводах для конкретного модуля, то они должны находиться в соответствующем им модуле (схоже как поступаем и с pages внутри модулей).

Либо вы можете хранить переводы рядом с модулем — в отдельной папке внутри модуля (например, i18n/ или locales/), не вынося их в common.

в common:

Логика common: тут хранятся общие несвязанные сущности, которые используются в нескольких модулях. Например, общие типы, функции, компоненты и т.д. В данном случае переводы весьма связанные между собой сущности

Соответственно хранение переводов в common не рекомендуется.

в global:

Здесь могут оказаться только специфичные сущности, которые вы можете расширить в рамках вашего фреймворка. Например, если вы используете Vue, то можете определить глобальные типы для Vue Router или других библиотек. Но не храните здесь сами переводы.

... типы?

Типы могут быть самыми разными, и, соответственно, относиться к ним можно тоже по-разному. Поэтому в первую очередь нужно определить, что это за типы и для чего они нужны.

в global:

Если вы хотите иметь типы, которые не требуют импорта и доступны глобально, то их можно размещать здесь.

в modules:

Если у вас есть множество типов, которые связаны логически, то модули — отличное место для их хранения.

в common:

Это совсем базовые и утилитарные типы, которые решают общие задачи и не имеют связи с бизнес-сущностями. Они должны вмещаться в один файл.

... UI-библиотеку?

Тут вопрос куда интереснее. Мы уже говорим о UI-библиотеке, как о нечто цельное или даже отдельной зависимостью, которое может быть частью модуля. Поэтому вполне логично хранить UI-библиотеку в модуле. Однако тут появляется закономерная проблема: если это модуль, то как мне ее использовать в common? Поэтому в данном случае лучше всего использовать IoC (Inversion of Control). Либо отказаться от использования UI-библиотеки в common.

... API (запросы к серверу)?

Запросы к API привязаны к бизнес-логике, поэтому в большинстве случаев их место — modules.

в modules:

Храните функции и клиенты, которые обращаются к конкретным эндпоинтам домена, внутри соответствующего модуля — например, в api/, services/ или repositories/. Типы ответов и DTO держите рядом с модулем. Наружу экспортируйте только то, что нужно страницам и другим модулям, через index.ts.

в app:

На уровне app остаётся инфраструктура: настройка HTTP-клиента, базовый URL, интерцепторы, авторизация, retry, интеграция с внешними SDK. Это не бизнес-запросы, а конфигурация и подключение транспорта. Подробнее см. App — интеграции.

в common:

В common допустимы только обезличенные утилиты без привязки к домену: обёртка над fetch, парсер ошибок, хелпер для query-string. Конкретные эндпоинты и контракты API сюда не выносят.

в global:

Не храните API-логику в global.

... layouts?

в app:

В абсолютном большинстве случаев стоит отдать предпочтение app-уровню для хранения layouts, так как layout — это надстройка над приложением и его страницей, а не составная часть какой-то страницы. То есть никто не должен импортировать layout напрямую.

Здесь же на уровне вы можете хранить основные кирпичики для построения layouts. Такие как header, footer, sidebar и т.д.

Также по умолчанию routes.ts должен находиться в app-уровне. Соответственно выбор используемого layout должен происходить в настройках роутера. Что же если вы используете файловый роутинг? Тогда для сохранения строгой типизации вам понадобится приложить больше усилий, так как импортировать layout напрямую нельзя. Ниже вы сможете прочесть, как поступать в данном случае.

в common:

Не рекомендуется хранить сами layouts, так как они не должны импортироваться никем, кроме app или указания в роутере. Если вы использовали FSD, то, возможно, появится желание разместить здесь хотя бы информацию о layouts. Однако это не рекомендуется, так как это попытка разбить проект по абстракции, а связанные сущности будут сильно оторваны друг от друга по принципу абстракции, а не назначения.

в modules:

А что если сделать модуль layouts? На самом деле этот вариант тоже имеет право на существование, так как он соответствует логике FEOD. Здесь можно определить существующие layouts и их описания с составными частями, а app/pages (в случае файлового роутинга) уже будут использовать их для построения страниц.

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

в global:

Если вы используете фреймворки с возможностью расширения интерфейсов, то можете определить layout как поле для роутинга в типах роутера (например, расширение RouteMeta во Vue Router). Сами компоненты layout здесь не размещают.