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

Куда положить файл

Алгоритм размещения кода: от корня композиции до promotion и shared.

Ответ на вопрос «куда положить этот файл» — не вкус, а последовательность предикатов. Пройдите шаги сверху вниз и остановитесь на первом совпадении.

Алгоритм

  1. Роут, layout, связка, провайдеры? → корень композиции: app/, pages/ или routes/. Только монтирование */public/*, без бизнес-логики.
  2. Другой модуль должен импортировать этот код (продуктовый сценарий)?services/<name>/public/. Promotion: перенос + пересмотр public API. Никогда feature → feature.
  3. Переносимый хелпер, UI или тип уже нужен 2+ модулям?shared/{ui,lib,api,model,domain}. Не выносите при первом использовании.
  4. Единственный потребитель — один модуль? → разместите рядом внутри этого модуля (внутренний файл или сегмент ui/, model/).
  5. Новый пользовательский сценарий, монтируемый только из корня композиции?features/<name> (стадия 0 — один файл, стадия 1+ — папка с public/).
  6. Ребро дало бы цикл или шаг вверх? → порт в public/ports.ts + привязка в корень композиции. Не используйте порт, чтобы обойти законную promotion.

Быстрые предикаты

ПутьПредикат
app/ / pages/ / routes/Собирает приложение; модули его не импортируют
features/Нет входящих рёбер от других модулей
services/Есть входящие рёбра от модулей; папку создают при первой promotion
shared/Переносимый код без продуктового сценария

Входящие рёбра для promotion — только от features/* и services/*. Монтирование из корня композиции promotion не вызывает.

Таблица: типичный файл → папка

У вас…КудаПример
page.tsx, +page.svelte, index.astroкорень композицииapp/page.tsx
Провайдеры, связка событийкорень композицииapp/providers.tsx
Экран / flow для роутаfeatures/<name>/public/ или стадия-0 файлcatalog-page.tsx
Store одного featureразместите рядом в featurecheckout.store.ts
Composable одного feature (Vue)разместите рядом в featureuse-catalog-search.ts
Логика для catalog и checkoutservices/<name>/public/cart.ts
Кнопка без продуктовой логикиshared/ui/button.tsx
formatCurrency, даты, i18nshared/lib/format-currency.ts
Базовый HTTP-клиент, interceptorsshared/api/http.ts
Эндпоинты одной фичиразместите рядом в featurecatalog.api.ts
Product, injection keyshared/model/ или shared/domain/product.ts
Unit-тест storeрядом с storecheckout.store.test.ts
E2Eкорень appe2e/ рядом с конфигом

services vs shared (последний ручной шаг)

Вопрос: «Мог бы этот код жить в другом продукте без изменений смысла?»

  • Даshared/ (форматирование, UI-примитив, тип)
  • Нетservices/ (корзина, сессия, каталог API как продуктовый сценарий)

При сомнении оставьте размещение рядом в feature до второго потребителя. Подробнее — Services vs Shared.

Склейка модулей

Feature не импортирует feature. Варианты:

ЗадачаМеханизм
Callback / propsкорень композиции передаёт между public/ entry
Событие без подписчиковpublic/*.events.ts; подписка в app/
Цикл / внешние начальные данныеpublic/ports.ts + привязка в app/

Разбор — Склейка модулей.

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

СитуацияПравило
Импорт вверх по слоямlayer-direction
features/afeatures/bfeature-to-feature
Импорт мимо public/public-api
Feature с входящими рёбрамиfeature-has-inbound
Кандидат в shared/servicesshared-candidate (doctor) — в help шаги Placement #2–3 по слою

Полный разбор ошибок — dma check: справочник нарушений.

Что дальше

On this page