Files
slm-design/docs/canons/decision-process.md
2026-07-24 14:35:35 +03:00

17 KiB
Raw Blame History

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 и архитектурные инварианты

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

Сбор контекста

Перед выбором места:

  1. Прочитай локальные инструкции приложения или package.
  2. Найди фактическую границу SLM: src/, apps/{app}/src или другой локальный root.
  3. Проверь существующие слои, группы и соседние модули. Не создавай новую параллельную структуру без необходимости.
  4. Проверь aliases, package exports и реальную разрешимость импортов.
  5. Найди текущих consumers, public API и runtime import graph изменяемой ответственности.
  6. Проверь существующие templates или generators после архитектурного выбора. Шаблон не принимает решение за SLM.
  7. Отдельно найди product I/O, hooks, stores, subscriptions, browser API и другие runtime-возможности.
  8. Проверь, нет ли уже 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, который полностью владеет ответственностью:

  1. Нужен одному component/module и не имеет самостоятельной ответственности: оставь внутри владельца.
  2. Нужен как самостоятельная часть одного module: создай nested module в parts/.
  3. Нужен нескольким частям одной page/route ветки: подними в общий composition scope этой ветки.
  4. Нужен нескольким composition modules и остаётся продуктовой композицией: создай отдельный composition module.
  5. Является доменным сценарием или product data boundary: создай или расширь business/{domain}.
  6. Является техническим сервисом: создай или расширь infra/{service}.
  7. Является универсальным UI: создай или расширь ui/{module}.
  8. Выноси в package только ui, infra или shared код с реальным вторым consumer либо явно зафиксированным межприложенческим ownership/reuse-контрактом.

Не поднимай код выше ради короткого импорта. Не создавай shared, общий provider, generic business context или package «на будущее».

Component, module и group

Применяй решение последовательно:

  1. Только отображает готовые props и не владеет зависимостями: component в ui/ родительского module.
  2. Владеет сценарием, данными, state, dependency, lifecycle или внутренней декомпозицией: самостоятельный module.
  3. Самостоятельный module, локальный для владельца: nested module в parts/.
  4. Папка только классифицирует конечные modules: group без index.ts, state и runtime logic.
  5. ui/, parts/, hooks/, types/, services/ и другие служебные папки внутри module: segments, а не modules.

Если component начинает получать данные, выбирать источник, вызывать сценарный hook или управлять процессом, не добавляй логику в component. Измени архитектурную форму сущности.

Выбор стратегии

Новый продуктовый сценарий

  1. Найди домен-владелец.
  2. Спроектируй {Domain}Api, доменные типы и доменные ошибки.
  3. Опиши минимальные runtime-capabilities в {Domain}Deps.
  4. Реализуй детерминированную доменную логику.
  5. Создай отдельные adapters в compositions/business/{domain}.
  6. Собери фабрику чистым builder.
  7. Подключи API во владельце lifecycle graph.
  8. Используй API из потребительских compositions.
  9. Добавь factory-level и assembly tests.

Прямой product I/O вне business boundary

Не расширяй существующее нарушение.

  1. Определи сценарий и домен.
  2. Перенеси контракт данных в business-owned Deps.
  3. Перенеси нормализацию, fallback и error mapping в business.
  4. Оставь concrete source call в dependency adapter.
  5. Замени прямой вызов на {Domain}Api.
  6. Закрой 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

  1. Собери каждый домен отдельным compositions/business/{domain} builder.
  2. Определи DAG cross-domain зависимостей.
  3. Выбери один явный lifecycle scope: application-lifetime composition, route, page, request или test. Слой app только подключает application composition.
  4. Создавай graph у владельца scope, а не в случайном screen/widget или на module scope без обоснования.
  5. Передавай consumers точный graph type. Не используй Partial<Graph> с приведением к полному типу.
  6. Для 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. Наличие возможной папки не означает, что её нужно создать.