Эволюция кода
Как архитектура растёт без переписывания: второе использование, promotion, сигналы doctor.
DMA не масштабируется заменой архитектуры на другую. Проект уплотняется на месте: папки появляются, когда предикаты начинают нести информацию.
Вы не создаёте пустой services/ «на вырост». Вы не выносите в shared/ «вдруг пригодится». Рост — ответ на факт второго потребителя или на сигнал инструментов.
Три фазы проекта
1. Старт
src/
├── app/ # или pages/, routes/
├── features/
└── shared/ # почти пустойНет services/. Все модули — изолированные фичи.
2. Первая promotion
Модуль стал нужен другому модулю → создаёте services/, переносите модуль, пересматриваете public API. С этого момента полезны проверки циклов внутри services.
Пример: catalog вызывает addToCart, checkout тоже — cart переезжает из feature в services/cart/.
3. Домены (когда services разросся)
Плотный подграф services или несколько команд → горизонтальный split (domains/billing/, отдельные пакеты). Новые вертикальные слои не добавляются.
Правило второго использования
| Что произошло | Действие |
|---|---|
| Feature нужен другому модулю | Promotion в services/ |
| Переносимый хелпер/UI нужен 2+ модулям | Lift в shared/ (нужная группа) |
Тип из public/ нужен 2+ модулям | Перенос в shared/domain |
import type — тоже ребро графа. Обход через типы не сработает.
Монтирование из корня композиции не считается входящие рёбра и promotion не вызывает.
Promotion — момент дизайна
Когда feature получает входящих рёбер от другого модуля, API, выросшее для одного потребителя, становится контрактом для многих. Это осознанный шаг:
- Перенести в
services/<name>/ - Пересмотреть
public/— что действительно нужно снаружи - Убедиться, что
dma checkзелёный (feature-has-inboundбольше не ругается)
Триггеры роста модуля
| Сигнал | Действие | Tooling |
|---|---|---|
Соседние файлы с общим basename-префиксом у стадия-0 (checkout.tsx + checkout.store.ts) | Стадия 1: папка + public/ | stage-growth |
| ~8+ внутренних файлов | Стадия 2: сегменты ui/model/api | stage-growth |
Раздутый public/ | Стадия 3: split модуля | Ревью (doctor пока нет) |
| Feature нужен модулю | Promotion → services/ | feature-has-inbound |
| Цикл между модулями | Порт + связка в app, или extract в shared | no-cycle |
| Плотный services подграф | Горизонтальный split | dense-services |
Пороги — дефолты CLI. В v1 нет user config для их изменения.
Сигналы dma doctor
Мягкие подсказки. Код выхода 0 — CI не ломают.
shared-candidate
Файл модуля импортируют 2+ других модуля. Часто это здоровая поверхность services/*/public/*. Перед перенос в shared/ спросите: это продукт или переносимый хелпер?
stage-growth
Структура модуля отстаёт от размера: file-модуль оброс соседний файл-файлами с тем же basename-префиксом, или в папке много файлов без сегментов ui/ / model/ / api/.
dense-services
Подграф services/ выглядит плотным — пора делить по доменам.
orphan-public
Файл в public/ никто не импортирует — мёртвый контракт. Удалите или сделайте внутренним.
Подробнее о команде — dma doctor.
При внедрении в существующий проект promotion и doctor-сигналы идут в связке с поэтапным планом — Миграция.