mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 15:30:16 +03:00
217 lines
17 KiB
Markdown
217 lines
17 KiB
Markdown
---
|
||
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](./file-atlas.md) |
|
||
| Задача затрагивает product I/O, source hook, domain store, event, lifecycle или external errors | [Runtime-граница business](./business-runtime-boundary.md) |
|
||
| Выполняется архитектурное ревью или финальная проверка реализации | [Архитектурная проверка](./validation.md) |
|
||
| Неясен layer, направление import или роль `app/compositions/business/infra/ui/shared` | [Слои](./layers.md) |
|
||
| Нужно отличить module, component, group, nested module или спроектировать public API | [Модули](./modules.md) |
|
||
| Проектируется factory, Api, Deps, domain error или сборка домена | [Business-фабрика](./business-factory.md) |
|
||
| Неясно размещение hook/store/service/mapper/provider/type/style | [Сегменты](./segments.md) |
|
||
| Решается вынос из `apps/*/src` в `packages/*` | [Монорепозитории](./monorepo.md) |
|
||
| Нужен полный пример adapters, builder, state runtime и graph lifecycle | [Business composition](../examples/business-composition.md) |
|
||
| Нужна матрица factory-level, assembly и colocated tests | [Тестирование business-модулей](../examples/business-testing.md) |
|
||
| Нужен page/route provider, локальный UI store и доступ к готовому graph | [Композиция через Provider](../examples/react/composition-provider.md) |
|
||
| Команда выбирает организацию groups внутри `compositions` | [Структуры compositions](../examples/react/composition-structures.md) |
|
||
|
||
Не используй карту как scaffold checklist. Наличие возможной папки не означает, что её нужно создать.
|