Derived Modular Arch
Концепции

Shared

Переносимый код: группы ui, lib, api, model, domain и правила роста.

shared/ — единственное место в проекте, где группировка по виду кода уместна. Здесь нет фич и продуктовых сценариев — только горизонтальные возможности, которые могли бы жить в любом приложении.

Всё в shared/ публично по умолчанию. Папка public/ здесь не нужна — слой существует, чтобы его импортировали.

Группы

shared/
├── ui/       # примитивы UI — без знания продукта
├── lib/      # чистые хелперы, обёртки инфраструктуры
├── api/      # транспорт: базовый клиент, interceptors
├── model/    # инфра-состояние: query client, env, flags
└── domain/   # общие бизнес-типы (опционально)
ГруппаКладитеНе кладите
ui/Кнопки, инпуты, layout-примитивы, иконкиКомпоненты, знающие про заказы/корзину
lib/Даты, форматирование, i18n, storageХелпер, нужный одному модулю
api/Базовый HTTP/WS клиент, auth plumbingЭндпоинты конкретной фичи
model/Query client, route constants, env/configБизнес-состояние фичи
domain/User, Money, branded IDТипы, принадлежащие одному модулю

Направление внутри shared

Группы имеют разную стабильность и разные права на импорты:

domain → (ничего)
lib    → domain
api    → lib, domain
model  → api, lib, domain
ui     → lib, domain     (✗ api, ✗ model)

ui не должен тянуть сеть или глобальное продуктовое состояние — иначе перестаёт быть переносимым примитивом.

lib vs api vs api фичи

Три разных места — частая путаница:

КодГдеПочему
get<T>() поверх fetchshared/api/http.tsТранспорт без знания эндпоинтов
fetchCatalogProducts()features/catalog/catalog.api.tsЭндпоинт одной фичи; другие модули не импортируют
formatCurrency()shared/lib/format-currency.tsЧистый хелпер, не сеть

Если второй модуль зовёт тот же продуктовый API (не transport) — promotion в services/, не в shared/api.

Пример в vite-react example.

Отличие от сегментов модуля

Имена совпадают (ui, model, api), но права разные:

  • features/checkout/ui может импортировать features/checkout/model
  • shared/ui не может импортировать shared/model

Модуль — изолированный остров со своими внутренними правилами. Shared — общий пласт с жёсткой портативностью.

Как код попадает в shared

Правило второго использования: не выносите «на будущее». Хелпер переезжает в shared/, когда его импортируют два и более модуля. Сигнал — shared-candidate в dma doctor.

Исключение: bootstrap-инфраструктура с первого дня (базовый API-клиент, env, первые UI-примитивы), если ими пользуется всё приложение.

Обратный путь

Если файл в shared/ имеет одного потребителя — верните его в модуль. Colocation дешевле, чем преждевременный shared. Это решение на ревью; отдельного doctor-правила пока нет.

Рост внутри группы

Как и модули, группы densify in place:

shared/ui/button.tsx  →  shared/ui/button/  (когда файлов стало много)

ui и api — наименее стабильные жители shared; первые кандидаты на вынос в пакет (стадия 4).

Граница shared vs services

Shared — переносимый код. Services — продуктовый сценарий с входящих рёбер от модулей. Если сомневаетесь — Services vs Shared.

Что проверяют инструменты

ПравилоЧто ловит
layer-directionИмпорт из shared «вверх»
shared-candidate (doctor)Файл импортируют 2+ модуля — пора подумать о перенос

Что дальше

On this page