17 KiB
title, description
| title | description |
|---|---|
| Процесс архитектурного решения | Обязательный порядок классификации задачи, выбора владельца, слоя, scope и стратегии изменений |
Процесс архитектурного решения
Не изменяй файлы, пока не принято архитектурное решение. Название папки, существующий похожий код и удобный импорт не доказывают правильность размещения.
Карточка решения
Перед реализацией определи:
| Вопрос | Что зафиксировать |
|---|---|
| Роль изменения | Framework wiring, продуктовый сценарий, интеграция, композиция интерфейса, технический сервис, UI или чистый фундамент |
| Владелец | Домен, route/page scope, composition module, infra-модуль, UI-модуль или локальный consumer |
| Данные | Продуктовые данные, техническое состояние, framework input, локальное UI-state или отсутствуют |
| Runtime-возможности | Источники данных, hooks, stores, SDK, browser API, events, clock, random, env и другие внешние capabilities |
| Место | Приложение или package, слой, модуль, вложенный модуль и сегмент |
| Публичная граница | Что действительно нужно экспортировать и кто будет consumer |
| Путь данных | От consumer до business API, dependency adapter и конкретного источника |
| Lifecycle | Кто создаёт instance, сколько instances допустимо и кто выполняет cleanup |
| Стратегия | Локальная правка, новый модуль, новый business-контракт, adapter, перенос или исправление public API |
| Проверки | Typecheck, тесты, import graph, public API, lifecycle и архитектурные инварианты |
Карточку не обязательно выводить пользователю, если решение очевидно. Но агент обязан уметь обосновать каждый пункт до изменения файлов.
Сбор контекста
Перед выбором места:
- Прочитай локальные инструкции приложения или package.
- Найди фактическую границу SLM:
src/,apps/{app}/srcили другой локальный root. - Проверь существующие слои, группы и соседние модули. Не создавай новую параллельную структуру без необходимости.
- Проверь aliases, package exports и реальную разрешимость импортов.
- Найди текущих consumers, public API и runtime import graph изменяемой ответственности.
- Проверь существующие templates или generators после архитектурного выбора. Шаблон не принимает решение за SLM.
- Отдельно найди product I/O, hooks, stores, subscriptions, browser API и другие runtime-возможности.
- Проверь, нет ли уже business-домена, которому принадлежит сценарий.
Не считай неиспользуемый provider, пустой context, тип будущего graph или ссылку на несуществующий домен готовой архитектурой. Решение должно быть достижимо из runtime entry point и иметь реальных consumers.
Выбор роли
Классифицируй ответственность в следующем порядке.
Framework wiring
Если код существует только из-за фреймворка, размести его в app:
- route-файл;
- bootstrap;
- framework error entry;
- подключение глобальных ресурсов;
- тонкое подключение готового composition module.
app не реализует продуктовую композицию, business graph, store, provider или экран.
Продуктовый сценарий
Если код определяет пользовательский сценарий, доменную модель, продуктовый state, бизнес-правило, нормализацию внешних данных, error mapping или доменный переход после ошибки, владелец находится в business/{domain}.
Визуальная реакция на готовый domain error принадлежит consumer composition: сообщение, error screen, redirect, retry control и UI fallback выбираются по стабильному доменному code.
Любой новый внешний источник продуктовых данных требует business-контракта. Колокация внешних вызовов в page/screen/widget services не является допустимым упрощением.
Интеграция business-домена
Если код реализует {Domain}Deps через SDK, HTTP, storage, browser API, state/query runtime, event bus или другой concrete runtime, размести его в compositions/business/{domain}.
Это интеграционный composition module, а не business-домен и не обычная page/screen/widget composition.
Продуктовая композиция
Если код собирает route/page/layout/screen/widget, управляет UI-state, provider scope или lifecycle готового business graph, размести его в соответствующем composition module.
Потребительский composition module получает продуктовые данные только через {Domain}Api. Он не импортирует product SDK, generated operations, product storage adapter или конкретный источник.
Технический сервис
Если код предоставляет техническую возможность без продуктовой модели и сценариев, размести его в infra:
- HTTP client;
- SDK wrapper;
- logger;
- theme engine;
- i18n engine;
- telemetry transport;
- технический realtime client.
Composition может использовать технический infra-сервис напрямую, если сервис не становится обходным путём к продуктовым данным. Если capability нужна business, она всё равно передаётся через business-owned deps и adapter.
Универсальный UI
Если сущность отображает интерфейс, не знает продуктовый сценарий и применима независимо от конкретной composition, размести её в ui.
Чистый фундамент
Если код детерминирован, не имеет runtime-state, не знает продукт и переиспользуется несколькими владельцами, рассмотри shared. По умолчанию оставляй код рядом с первым владельцем.
Выбор scope
Выбирай минимальный scope, который полностью владеет ответственностью:
- Нужен одному component/module и не имеет самостоятельной ответственности: оставь внутри владельца.
- Нужен как самостоятельная часть одного module: создай nested module в
parts/. - Нужен нескольким частям одной page/route ветки: подними в общий composition scope этой ветки.
- Нужен нескольким composition modules и остаётся продуктовой композицией: создай отдельный composition module.
- Является доменным сценарием или product data boundary: создай или расширь
business/{domain}. - Является техническим сервисом: создай или расширь
infra/{service}. - Является универсальным UI: создай или расширь
ui/{module}. - Выноси в package только
ui,infraилиsharedкод с реальным вторым consumer либо явно зафиксированным межприложенческим ownership/reuse-контрактом.
Не поднимай код выше ради короткого импорта. Не создавай shared, общий provider, generic business context или package «на будущее».
Component, module и group
Применяй решение последовательно:
- Только отображает готовые props и не владеет зависимостями: component в
ui/родительского module. - Владеет сценарием, данными, state, dependency, lifecycle или внутренней декомпозицией: самостоятельный module.
- Самостоятельный module, локальный для владельца: nested module в
parts/. - Папка только классифицирует конечные modules: group без
index.ts, state и runtime logic. ui/,parts/,hooks/,types/,services/и другие служебные папки внутри module: segments, а не modules.
Если component начинает получать данные, выбирать источник, вызывать сценарный hook или управлять процессом, не добавляй логику в component. Измени архитектурную форму сущности.
Выбор стратегии
Новый продуктовый сценарий
- Найди домен-владелец.
- Спроектируй
{Domain}Api, доменные типы и доменные ошибки. - Опиши минимальные runtime-capabilities в
{Domain}Deps. - Реализуй детерминированную доменную логику.
- Создай отдельные adapters в
compositions/business/{domain}. - Собери фабрику чистым builder.
- Подключи API во владельце lifecycle graph.
- Используй API из потребительских compositions.
- Добавь factory-level и assembly tests.
Прямой product I/O вне business boundary
Не расширяй существующее нарушение.
- Определи сценарий и домен.
- Перенеси контракт данных в business-owned
Deps. - Перенеси нормализацию, fallback и error mapping в business.
- Оставь concrete source call в dependency adapter.
- Замени прямой вызов на
{Domain}Api. - Закрой adapter и source details из public API.
Новый store или dependency hook
Сначала определи, является state локальным UI-state или доменным state.
- State является локальным UI-state, если сбрасывается вместе с UI scope, управляет только представлением и не хранит продуктовый факт или product data cache. Такой state может принадлежать composition module и использовать выбранный state manager внутри владельца.
- State является доменным, если выражает продуктовый факт, инвариант, доступен через business API или участвует в бизнес-сценарии.
- Доменный state принадлежит business-контракту. Фабрика получает state adapter factory через
deps, выбирает initial domain state и создаёт concrete port через adapter. - Source/query hook реализуется adapter-ом; business вызывает только dependency hook и возвращает собственный доменный hook/result.
Сборка graph
- Собери каждый домен отдельным
compositions/business/{domain}builder. - Определи DAG cross-domain зависимостей.
- Выбери один явный lifecycle scope: application-lifetime composition, route, page, request или test. Слой
appтолько подключает application composition. - Создавай graph у владельца scope, а не в случайном screen/widget или на module scope без обоснования.
- Передавай consumers точный graph type. Не используй
Partial<Graph>с приведением к полному типу. - Для subscriptions, timers и resources зафиксируй cleanup/dispose.
Архитектурное ревью
Проверяй не только пути файлов, но и семантику:
- business-shaped код вне
business; - product graph в
infra; - type-only imports, которые фактически переносят ownership;
- provider, который не создаёт и не получает instance от явного владельца;
- orphan modules и providers, недостижимые из entry point;
- public API, раскрывающий raw store, context, adapter или generated types;
- отсутствующие tests обязательного business-контракта.
Условия остановки
Останови реализацию и сначала исправь решение, если:
- владелец ответственности не определён;
- один state или source имеет несколько конкурирующих владельцев;
- business требует прямого runtime или type-only import concrete runtime;
- graph создаёт runtime-цикл;
- lifecycle instance или cleanup не определён;
- public API нужен только для обхода границы;
- шаблон генерирует архитектуру, противоречащую принятому решению;
- изменение требует незапрошенной миграции нескольких независимых областей.
Локальные материалы
Основной процесс достаточен для типового решения. Открывай только материал, который нужен текущей ветке задачи.
| Ситуация | Материал |
|---|---|
| Нужна полная карта допустимых файлов, root entries, segments и tests | Атлас файлов SLM |
| Задача затрагивает product I/O, source hook, domain store, event, lifecycle или external errors | Runtime-граница business |
| Выполняется архитектурное ревью или финальная проверка реализации | Архитектурная проверка |
Неясен layer, направление import или роль app/compositions/business/infra/ui/shared |
Слои |
| Нужно отличить module, component, group, nested module или спроектировать public API | Модули |
| Проектируется factory, Api, Deps, domain error или сборка домена | Business-фабрика |
| Неясно размещение hook/store/service/mapper/provider/type/style | Сегменты |
Решается вынос из apps/*/src в packages/* |
Монорепозитории |
| Нужен полный пример adapters, builder, state runtime и graph lifecycle | Business composition |
| Нужна матрица factory-level, assembly и colocated tests | Тестирование business-модулей |
| Нужен page/route provider, локальный UI store и доступ к готовому graph | Композиция через Provider |
Команда выбирает организацию groups внутри compositions |
Структуры compositions |
Не используй карту как scaffold checklist. Наличие возможной папки не означает, что её нужно создать.