Методология
Этот документ описывает, как организована спецификация проекта ICD, где что лежит и как с ней работать — разработчику, ИИ-ассистенту и любому, кто читает спеку впервые.
Спека — источник правды для устройства системы. Цель — не подробное руководство, а такой набор фактов, который не противоречит коду, не устаревает быстро и собирается в одно место для понимания.
1. Spec first
Спецификация — источник правды. Изменение поведения системы начинается с изменения спеки, не с правки кода. Реализация догоняет спеку, не наоборот.
Раскрывается по мере проработки.
2. AI first + optimized
ИИ-агент — первоклассный читатель спеки наравне с человеком. Формат, размеры порций, способ ссылаться — выбираются так, чтобы агент мог их потреблять без потерь. Оптимизация под агента — это compile-safe идентификаторы, плоские ссылки nameof(), отсутствие неявных соглашений «из контекста».
Раскрывается по мере проработки.
3. C# compilable
Спецификация оформлена как компилируемый C#-проект. Это даёт линтер: ссылка на несуществующий артефакт ломает сборку, переименование через F2 правит все упоминания, find-references показывает обратные связи без отдельных индексов.
Раскрывается по мере проработки.
4. Human readable
C#-формат не отнимает у человека читаемости. Генератор превращает классы в навигируемый HTML-сайт с прозой, таблицами, диаграммами и кликабельными ссылками между артефактами. Любой раздел доступен по стабильному URL и пригоден для копи-пейста в чат, обсуждение, ревью.
Раскрывается по мере проработки.
5. Материализация прозы
Любой кусок прозы, на который команда уже ссылается («FR-301», «БП-3», «журнал оцифровки», «карточка информационной карты») — кандидат на материализацию в отдельный класс-артефакт. До материализации это текст; после — адресуемая сущность с собственной страницей, секциями, обратными ссылками. Vision-таблицы — пример прогрессивной материализации: текст «FR-301» в ячейке становится ссылкой на IFunctionalRequirementDocument-класс ровно тогда, когда требование берётся в проработку.
Раскрывается по мере проработки.
6. Правило одного факта
Каждый факт описан ровно в одном месте. Если он нужен в двух — второе место это ссылка (typed-reference, не копия).
Из этого правила следует много частностей:
- Глоссарий — один на проект; vision/use-case/handler ссылаются на термины через
nameof(), не пересказывают определения. - Domain-сущность — в одном bounded context; другие места ссылаются через
nameof(). - Описание операции — в handler-spec; endpoint, service, use-case ссылаются на него, не дублируют narrative.
- Acceptance criteria — на одном уровне (эпик или use-case); не дублировать на оба.
- Open question (OQ-N) — в одном месте (readme эпика); use-case/page/handler ссылаются.
- Status, version, comments — в самом артефакте; никаких «параллельных трекеров».
🔍 Признак нарушения: если поиск по точной фразе в проекте даёт ≥2 result-а в разных артефактах — это дрейф. Нужно: оставить ровно один источник правды, остальные превратить в typed-ссылку (
nameof,SdaidAnchors.RefTo<T>(text),GlossaryAnchors.RefTo<T>(text)).
7. Язык прозы: спека читается заказчиком
Спецификация — не только внутренний документ. Её разделы цитируют в переписке, переносят в
документы для заказчика, показывают на согласованиях. Заказчик знает «информационную карту»,
«пакет ввода», «уверенность распознавания» — и не обязан знать InformationMap, InputPacket,
RecognitionConfidence.
Правило: идентификаторы классов не выводятся в прозу.
| Что | Как писать |
|---|---|
| Термин глоссария | GlossaryAnchors.RefTo<Term>("русская словоформа") — ссылка типизированная, на экран идёт заданный текст, склоняемый по месту |
| Ссылка на артефакт (FR, ADR, эпик, страница) | по-русски в тексте; код — в блоке «Связанные артефакты» в конце документа |
| Технические таблицы (трассировка, структура) | коды допустимы: эти таблицы адресованы разработчику |
Почему не «просто nameof() везде»: nameof() даёт compile-safe ссылку, но отображается
именем класса. Это правильный инструмент для трассируемости и неправильный — для текста,
который читает человек со стороны заказчика.
Разделение «этот документ увидит заказчик, а этот нет» на практике не держится: куски цитируют и переносят. Поэтому правило действует на всю спеку, а не на её «внешнюю» часть.
7.1. Блок «Связанные артефакты»
Ссылки, вынесенные из прозы, собираются в конце документа отдельной секцией:
## 8. Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Requirements.FR_003_AttributeExtraction)} — Сегментация и извлечение атрибутов
Полное имя типа в nameof() избавляет от лишних using, а обратные связи генератора
строятся по тем же упоминаниям, что и раньше.
9. Что куда писать / структура папок
Где живёт vision, глоссарий, требования, эпики, доменные сущности, архитектурные решения — фиксируется явно. Цель: и человек, и ИИ-агент однозначно знают, куда положить новый артефакт и где искать существующий — без догадок.
9.1. Принципы (зачем именно так)
Один домен — icd. Вся предметная область — единый домен. Чтобы группировать сущности, домен поделён на доменные модули (50_Domain/<Module>/: Capture, Recognition, Verification, …). Это группировка по смыслу, а не отдельные Bounded Contexts. Поэтому контракт API и UI-спека живут на корне спеки (45_Api, 65_Ui), а не внутри доменных модулей.
Слои — горизонтальные, на корне. Каждый слой живёт в своём top-level разделе: контракт API — 45_Api, домен — 50_Domain, поведение бэка — 55_Backend, эпики — 60_Epics, UI-спека — 65_Ui. Внутри раздела — группировка по области/эпику (Endpoints/<Area>/, Handlers/<Area>/, Pages/<Area>/).
Эпик — тонкая нить. 60_Epics/<Epic>/ собирает сквозной контекст (use-case-ы, локальные решения, AC, state-machine-ы) и ссылается на артефакты слоёв через nameof(). Реализационные артефакты use-case-а (endpoint, handler, страница) лежат в своих слоях, не в подпапках use-case-а.
Реализация — 80_Subsystems, по необходимости. Особенности конкретной реализации деплой-единицы (ADR подсистемы, нюансы стека) — в 80_Subsystems/. Если особенностей нет — раздел не заводится. Инфраструктурные контракты (file/MQ/integration) — в 70_Infrastructure/, тоже по необходимости (сейчас файловое хранилище живёт в 55_Backend/Files).
9.2. Структура (верхнеуровневые разделы)
00_Methodology/ как работать со спекой
00_Migration/ инструкция миграции md→SDAID (временная, пока идёт перенос)
10_Vision/ слабоструктурированный вход от заказчика
20_Glossary/ общий проектный словарь
30_Requirements/ FR — материализуются по мере проработки
40_Arch/ системные кросс-cutting решения
ADR/ общесистемные ADR
Tech/CSharp/ техно-ADR backend
Tech/Angular/ техно-ADR frontend
45_Api/ контракт API
Based/ общие базовые типы (BasedCommand, BasedQuery, …)
Endpoints/<Area>/ endpoint + Command/Query + Result
50_Domain/ домен icd, поделён на доменные модули
<Module>/
BC_<Module>.cs overview модуля (IBoundedContextModuleDocument)
<Entity>.cs сущности и агрегаты
55_Backend/ поведение бэка
Handlers/<Area>/ canonical narrative операций (IHandlerDocument)
Services/<Area>/ доменные / прикладные сервисы
Import/, Files/ сервисы импорта, файловое хранилище
60_Epics/ бизнес-направления (тонкая нить)
<Epic>/
EPIC_<Epic>.cs цель, акторы, scope, открытые вопросы, AC
UseCases/<UC>/ use-case-документы (Given/When/Then), state-machine-ы
65_Ui/ UI-спека
Pages/<Area>/ страницы (IPageDocument)
Componetns/, Layout/ компоненты и layout-ы
70_Infrastructure/ [при необходимости] инфраструктурные контракты (file/MQ/integration)
80_Subsystems/ [при необходимости] особенности реализации деплой-единиц
9.3. Папка → kind-маркер (что внутри какой папки)
| Папка | Kind-маркер класса | Что внутри |
|---|---|---|
00_Methodology/ |
ISdaidDocument (без подтипа) |
Документ методологии проекта (этот файл) |
00_Migration/ |
ISdaidDocument (без подтипа) |
HowTo миграции md→SDAID (временно) |
10_Vision/ |
IVisionDocument |
Vision-документ (один) |
20_Glossary/Terms/ |
IGlossaryTerm (отдельный контракт, не ISdaidDocument) |
Термины проекта, по одному классу на термин |
30_Requirements/ |
IFunctionalRequirementDocument |
FR-документы (материализуются по мере проработки) |
40_Arch/ADR/ |
IAdrDocument |
Общесистемные ADR |
40_Arch/Tech/<Stack>/ |
IAdrDocument |
Техно-ADR (CSharp, Angular) |
45_Api/Endpoints/<Area>/ |
IApiContractDocument (+ IFolder<ApiFolder>) |
Endpoint + Command/Query + Result |
45_Api/Based/ |
ISdaidDocument |
Общие базовые типы команд/запросов |
50_Domain/<Module>/ (overview) |
IBoundedContextModuleDocument |
Описание доменного модуля |
50_Domain/<Module>/<Entity>.cs |
IDomainEntityDocument |
Сущности и агрегаты (агрегаты пока без отдельного маркера) |
55_Backend/Handlers/<Area>/ |
IHandlerDocument (+ IFolder<BackendFolder>) |
Canonical narrative операции |
55_Backend/Services/<Area>/, Import/, Files/ |
ISdaidDocument / IDomainServiceDocument (+ IFolder<BackendFolder>) |
Сервисы реализации, импорт, файловое хранилище |
60_Epics/<E>/ (readme) |
IEpicDocument |
Цель эпика, акторы, scope, открытые вопросы, AC |
60_Epics/<E>/UseCases/<UC>/ |
IUseCaseDocument (с PrimaryActor) |
Given/When/Then narrative |
60_Epics/<E>/UseCases/<UC>/StateMachines/ |
IStateMachineDocument |
State machine с mermaid в Body |
65_Ui/Pages/<Area>/ |
IPageDocument (+ IFolder<UiFolder>) |
Спека страницы |
65_Ui/Componetns/, Layout/ |
ISdaidDocument / IUiSpecDocument (+ IFolder<UiFolder>) |
Компоненты, layout-ы |
70_Infrastructure/<Capability>/ |
IInfrastructureDocument |
Контракты на file/MQ/integration (по необходимости) |
80_Subsystems/<S>/ |
IAdrDocument и др. (+ IFolder<SubsystemsFolder>) |
Особенности реализации подсистемы (по необходимости) |
9.4. Что куда — детально
| Что за артефакт | Где живёт |
|---|---|
| Доменная сущность / агрегат | 50_Domain/<Module>/<Entity>.cs |
| Endpoint + Command/Query + Result | 45_Api/Endpoints/<Area>/ |
| Handler (canonical narrative) | 55_Backend/Handlers/<Area>/ |
| Доменный / прикладной сервис | 55_Backend/Services/<Area>/ |
| Инфраструктурный контракт | 70_Infrastructure/<Capability>/ (по необходимости) |
| Страница / компонент UI | 65_Ui/Pages/<Area>/, 65_Ui/Componetns/ |
| Use-case (Given/When/Then) | 60_Epics/<E>/UseCases/<UC>/ |
| State machine | 60_Epics/<E>/UseCases/<UC>/StateMachines/ |
| Системный ADR | 40_Arch/ADR/ |
| Tech ADR (Angular/EF/CSharp) | 40_Arch/Tech/<Stack>/ |
| Особенности реализации подсистемы | 80_Subsystems/<S>/ (по необходимости) |
9.5. Эпик ↔ доменный модуль ↔ слой
Разные сущности, связанные через ссылки:
- Эпик (
60_Epics/<E>/) — бизнес-направление, единица планирования; тонкая нить со сквозным контекстом. - Доменный модуль (
50_Domain/<Module>/) — группа сущностей одного домена icd. - Слой (
45_Api,55_Backend,65_Ui) — артефакты реализации по типу.
Один эпик затрагивает ≥1 доменный модуль и артефакты нескольких слоёв; модуль/слой обслуживают ≥1 эпик. Связи трассируются через nameof() в обе стороны.
10. Границы ответственности, контракты и соглашения
Каждый модуль (домен, API, backend, UI) отвечает за свою часть знаний и не лезет в чужую. На границах модулей фиксируются явные контракты (что один модуль обещает другому) и соглашения (как они общаются: имена, формат данных, ошибки, идемпотентность). Изменение контракта — событие, требующее согласования; изменение внутреннего устройства модуля — внутреннее дело модуля.
Раскрывается по мере проработки.
11. Эпики и слои
Эпик — это бизнес-цель, реализация которой проходит через несколько слоёв (домен, API, backend, UI).
Артефакты слоёв первичны — сущность, страница, endpoint существуют сами по себе и в принципе могут участвовать в нескольких эпиках. Эпик их не «забирает себе», а собирает вместе и добавляет cross-cutting контекст (use-case, state-machine, AC, локальные решения).
Артефакт физически группируется в подпапку с именем эпика, если он относится только к этому эпику:
Modules/Api/Endpoints/<Epic>/<Name>/— endpoints эпика.Modules/Backend/Handlers/<Epic>/...— хендлеры эпика.Modules/Backend/Services/<Epic>/...— сервисы и алгоритмы эпика (см. Сервисы и алгоритмы).Modules/Ui/Pages/<Epic>/...— страницы эпика.Modules/Domain/— сущности обычно живут в общем домене (часто переиспользуются между эпиками).
Если артефакт начинает использоваться в нескольких эпиках — он поднимается на верхний уровень слоя (без эпик-подпапки) и его эпик-специфичные ссылки выносятся в epic-уровень.
Epics/<Epic>/ — тонкая нить: epic-документ, use-cases, state-machines, локальные решения, AC. Содержит только то, что характерно для пересечений слоёв; не дублирует их.
11.1. Ссылки артефактов слоя на эпик
Артефакт, физически лежащий в <Epic>/-подпапке, может ссылаться на локальные документы эпика (OQ-N в readme.md, R-N в adr.md, AC-* в acceptance.md). Это естественный контекст: пока артефакт привязан к эпику, его комментарии живут в эпик-словаре.
Если артефакт перестаёт быть эпик-специфичным — все ссылки на эпик надо вычистить до перемещения наверх. Артефакт без эпика не должен ссылаться на конкретный эпик-readme.
12. Структурные связи между документами
Зависимости между документами фиксируются явно — типизированными структурными связями
(IHasStructuralLinks + Rel.Serves<T>() / Rel.Realizes<T>() / Rel.Uses<T>()), а не
только ссылками в прозе. Это отдельный курируемый слой: генератор рендерит его блоком
«Структурные связи» на странице документа и сводит в страницу «Граф связей» (по одной
диаграмме на эпик). Объявлять связи — обязательная часть спеки, а не опция: каждый
документ, зависящий от другого, должен нести эту зависимость явно.
12.1. Правило направления — от вариативного к стабильному
Связь объявляется на более вариативной стороне и указывает на более стабильную. Стрелки всегда идут «сверху вниз» по шкале вариативности ниже (от того, что меняется часто, к тому, что меняется редко) — так одно правило объясняет направление всех зависимостей разом. Обратную подпись («используется» / «реализуется» / «обслуживается») генератор строит сам на целевой странице.
Шкала вариативности (по убыванию; чем выше — тем чаще меняется):
| Понятие | Вариативность | Что гонит изменчивость |
|---|---|---|
| page | 9 | UI: редизайны, перекладка полей и флоу — постоянно |
| use_case | 8 | продуктовый сценарий, меняется вместе с требованиями |
| epic | 7 | стратегическая группировка, тасуется с роадмапом |
| endpoint | 7 | контрактная поверхность; плодится версиями (но опубликованное — липкое) |
| read_model | 6 | форма диктуется запросами и UI |
| consumed_contract | 6 | чужой контракт, меняется вне твоего контроля |
| message (Command/Query) | 5 | внутренний DTO, следует за входами операции |
| domain_event | 5 | публикуется наружу → как контракт, липкий |
| handler | 4 | оркестрация; сама операция стабильна |
| policy | 4 | бизнес-правила — самая подвижная часть домена |
| domain_service | 3 | доменная координация, стабильна вместе с моделью |
| aggregate | 3 | граница консистентности, ядро инвариантов |
| entity | 2 | живёт внутри агрегата |
| value_object | 1 | примитивы-бедрок (Money, ISBN, DateRange) |
| bounded_context | 1 | сама граница; перечерчивается крайне редко |
12.2. Типовые связи (источник → цель, вниз по шкале)
- page → use_case / epic —
Rel.Realizes<…>()(страница реализует сценарий; если use-case-документа нет — реализует эпик напрямую). - use_case / epic → endpoint —
Rel.Uses<…>()(сценарий/эпик использует endpoint-ы). - endpoint → handler —
Rel.Uses<…>()(контракт опирается на обслуживающий хендлер).
Rel.Serves (подпись «обслуживает») семантически закреплён за направлением handler →
endpoint. Так как handler стабильнее endpoint-а, в этом проекте мы объявляем связь на
вариативной стороне — endpoint → handler через Rel.Uses (а обратная подпись
«используется» появляется на странице хендлера автоматически).
13. Проверки качества документа (обязательны при каждом касании)
При создании или правке любого документа спеки проверять три пункта — и озвучивать замечание, если что-то не так (не молча):
Структурные связи проставлены. Документ, зависящий от другого, обязан нести связь явно (
IHasStructuralLinks, см. раздел «Структурные связи»). Забыли проставить — напомнить и доставить.Проза, а не структурное описание. Тело документа пишется связной прозой (по материализации прозы из секций), а не «структурным конспектом»/списком ради списка. Встретили документ, написанный структурным описанием вместо прозы — по возможности переписываем в прозу; как минимум — напоминаем.
Нет документов-сирот. У документа должна быть хотя бы одна связь — исходящая или входящая (на него кто-то ссылается, либо он ссылается). Встретили полностью изолированный документ (никто не ссылается и он ни на кого) — напомнить: либо вписать его в граф, либо это сигнал, что документ лишний/недописан.
14. Запросы на изменение (Change Request)
Замечания после тестирования и пожелания пользователей оформляются батчами в документы
kind IChangeRequestDocument (папка «Запросы на изменение», 35_ChangeRequests/). Цель —
трассируемость: по системе видно, почему принято то или иное решение.
Один батч = один документ. Имя — дата + тема: DOC_CR_<гггг>_<мм>_<дд>_<Тема>. Новые
замечания другой даты — новый документ (старый не правим задним числом, кроме раздела «Итог»).
Структура CR-документа:
- Источник (как есть) — дословный текст замечаний/пожеланий (verbatim: язык и опечатки сохраняем, как при миграции).
- Разбор — что меняем по каждому пункту, детали, открытые вопросы.
- Blast radius — на что влияет: спеки (страницы/компоненты/endpoint/use-case), backend,
UI, миграции/swagger. Затронутые документы объявляются структурными связями
(
Rel.Uses<…>) — тогда влияние видно на «Графе связей», а на затронутых страницах появляется обратная ссылка на CR. - План — шаги (спека → реализация), чек-лист.
- Итог — что фактически сделано (со ссылками) + статус по каждому пункту:
выполнено/отложено/в работе.
Ссылки в прозе. Кроме раздела «Источник (как есть)» (он verbatim), в прозе CR на каждую
упоминаемую страницу/компонент/endpoint/use-case/термин ставим навигационную ссылку
(SdaidAnchors.RefTo<…>() в interpolated-строке, либо markdown-ссылка), чтобы из CR можно
было перейти к затронутому документу. Затронутые документы дополнительно объявляем
структурной связью Rel.Uses<…> (blast radius на графе).
UI-изменения — сначала макет. Если CR меняет внешний вид страницы, сперва обновляем (или создаём) её wireframe-мокап, показываем пользователю и получаем «ок», и только затем идём в реализацию. Не начинаем кодить визуальную часть, не показав, что получится.
Процесс: оформили CR из «как есть» → обсудили разбор/blast radius/план → идём по нему (спека + мокап для UI → согласование → реализация) → фиксируем «Итог» и откладываем CR (статус). Так батчи замечаний не перемешиваются и каждое решение прослеживается до источника.
15. Сервисы и алгоритмы
В backend бывают абстракции, которые не являются ни хендлерами, ни сущностями: сервисы (например, оркестрация обработки файла, отправка уведомлений) и алгоритмы (rusmarc-парсер, экстрактор полей, расчёт markup-критериев). Их тоже надо специфицировать.
15.1. Расположение
Сервисы и алгоритмы живут в слое 55_Backend (см. M_S6_FolderStructure):
- Доменный / прикладной сервис — в
55_Backend/Services/<Area>/<Name>Service.cs. - Сервис импорта — в
55_Backend/Import/<Name>Service.cs. - Файловое хранилище — в
55_Backend/Files/<Name>Storage.cs. - Инфраструктурный контракт (MQ, внешние интеграции — контракт на работу с внешней капабилити) — в
70_Infrastructure/<Capability>/, по необходимости. - Алгоритмы (чистая трансформация данных без побочных эффектов) — рядом с сервисом, который их использует. Отдельный
Algorithms/-уровень не вводим, пока их немного.
15.2. Что в C#-файле сервиса
- Класс (НЕ интерфейс) — чтобы методы могли иметь тело. Класс — spec-only, не используется в runtime, не реализует интерфейсы и не содержит реальной логики.
- XML-doc на классе — назначение сервиса и архитектурная привязка (кто оркестратор, как сервис связан с state machine).
- XML-doc на каждом методе — короткий контракт: что делает (1-3 предложения), что возвращает, что НЕ трогает.
- Тело каждого метода:
var spec = $@" КАК выполнить: 1. ... 2. ... ЧЕГО НЕ ДЕЛАТЬ: - ... "; throw new NotImplementedException(spec);- Cтрока
spec— детальные шаги «КАК» (нумерованным списком), затем «ЧЕГО НЕ ДЕЛАТЬ», инварианты (например, оптимистическая блокировка). throw new NotImplementedException(spec)— гарантирует, что (а)specне unused (без warning'а), (б) при ошибочном вызове в реальной сборке текст spec попадёт в exception message.
- Cтрока
- Result-классы для результатов методов — отдельные публичные типы рядом, в том же файле.
15.3. Почему класс, а не интерфейс
Конвенция «комментарий говорит ЧТО, тело говорит КАК» — естественная и ценная. Интерфейс не позволяет описать «КАК должен работать метод», только сигнатуру и контракт. Если запихать «КАК» в XML-doc, получается смешение «что» и «как» в одном комментарии — теряется смысловое разделение.
Класс с пустыми телами решает это естественно: XML-doc метода = «что», тело со spec = «как». Реализация в src-api — отдельная задача и отдельный класс.
Spec-классы:
- живут в
Ban.Sdaid.Icd.csproj, не подтягиваются в runtime-solution; - не реализуют никаких интерфейсов (это забота src-api);
- не имеют конкретной логики — только
spec-строки иthrow NotImplementedException(spec).
15.4. Когда всё-таки interface
Если у спеки нет «как» (только контракт типа маркера или dispatch-точки) — допустим интерфейс. Но когда в спеке есть существенный «КАК», — это сигнал использовать класс.
15.5. Что НЕ делать
- Не реализовывать тела методов. Это спека, не прототип.
- Не описывать в XML-doc высокоуровневые сценарии — это в use-case или state machine, со ссылкой.
- Не дублировать содержимое state machine в спеке сервиса. Сервис описывает что делает шаг, state machine — когда какой шаг разрешён.
15.6. Связка сервис ↔ хендлер ↔ state machine
Типичная картина для сервисов с состоянием (как IImportJobServiceSpec):
- state machine (md) — какие статусы есть, какие переходы разрешены, когда какой переход.
- сервис (interface, cs) — что конкретно происходит на конкретном шаге (verify, process batch); сервис не знает о state machine и о конкретном вызывающем — может переиспользоваться.
- хендлер (cs) — каноническое описание «что произойдёт при вызове endpoint» (см. Каноническое описание операции). Проводит читателя сквозь всю цепочку, интегрирует state machine и сервис в цельный рассказ.
Все три ссылаются друг на друга через nameof() / md-путь / <see cref>. Хендлер не дублирует содержимое state machine и XML-doc сервиса, но интегрирует их: где сервис делает «что», state machine задаёт «когда какой переход», а хендлер связывает их в «при вызове endpoint происходит вот это».
16. Каноническое описание операции
Когда читатель приходит со вопросом «что произойдёт, если дёрнуть этот endpoint», он должен в одном месте получить полную картину — без необходимости стыковать обрывки из 4-х файлов.
Этим местом является handler-spec — string spec в <Name>Handler.cs.
16.1. Распределение описания
| Файл / артефакт | Где живёт | Что описывает |
|---|---|---|
Endpoint (<Name>Endpoint.cs) |
45_Api/Endpoints/<Area>/ |
Назначение операции в 1-3 предложения + указатель на handler через <see cref>. Не описывает поля — XML-doc каждого поля живёт на самом поле в Query/Command/Result. |
Handler (<Name>Handler.cs) |
55_Backend/Handlers/<Area>/ |
Каноническая narrative — проводит сквозь всю цепочку: валидация входа → загрузка данных → ветвление по состоянию → вызовы методов сервиса (с краткими 1-2-предложными summary) → применение результата → сохранение → ответ. Реакции на ошибки и не-happy-path тоже здесь. |
Service (<Name>Service.cs) |
55_Backend/Services/<Area>/ (или Import/, Files/) |
Что конкретно делает один шаг — без оркестрационного контекста и без ссылок на конкретного вызывающего (сервис может переиспользоваться). Класс, не интерфейс (см. соответствующую секцию). |
State machine (<Aggregate>StateMachine.cs) |
60_Epics/<E>/UseCases/<UC>/StateMachines/ |
Какие статусы и переходы разрешены, при каких условиях. Не содержит «как работает конкретный handler». Описывается через ISdaidDocumentSection с mermaid в Body. |
| Use case | 60_Epics/<E>/UseCases/<UC>/ (сам класс use-case-а как IUseCaseDocument + Given/When/Then секциями) |
Сквозной сценарий пользователя через несколько endpoint-ов. Если операция ограничена одним endpoint — use-case не нужен. |
16.2. Что НЕ делать
- Не делать handler «самым тонким». Тонкий handler делает читателя несчастным: full story рассыпается. Handler не дублирует детали сервиса/state-machine, но интегрирует их.
- Не плодить overview-документы. Если возникает соблазн написать
ProceedImportJob.overview.md— это плохой признак: содержание должно жить в handler-spec, а не рядом. - Не дублировать narrative в endpoint-XML-doc. Endpoint описывает только назначение (1-3 предложения) и указывает на handler. Полное описание — в handler-spec.
- Не описывать поля endpoint в endpoint-summary. Каждое поле имеет своё XML-doc на самом себе — там и хранится описание (тип, default, диапазон, связанные правила). Дубликат в summary endpoint-класса гарантирует drift: при изменении поля никто не вспомнит поправить summary. Если нужна компайл-сейфти ссылка —
<see cref>на тип поля в endpoint summary, а не пересказ. - Не описывать в сервисе оркестрационный контекст. Сервис рассказывает «что я делаю на одном вызове моего метода», без знания о том, кто и в каком порядке его вызывает. Иначе он перестаёт быть переиспользуемым.
16.3. Длина handler-spec
- Для query-эндпоинтов с простой логикой — 3-7 шагов, 10-20 строк.
- Для command-эндпоинтов с workflow / state machine — 30-60 строк, и это нормально: handler остаётся единственным местом «что произойдёт».
16.4. Ссылки в spec-строках — inline nameof / RefTo
nameof(...) и SdaidAnchors.RefTo<...>() ставятся inline в spec-строках.
Прежнее требование выносить их в class-level const-алиасы отменено: спека
читается в сгенерированном HTML (не в исходнике), а алиасы создают лишний шум при
чтении кода (в т.ч. для ИИ-агента). При необходимости подключаем namespace через using.
17. Страницы и формы (page / modal / drawer)
Страница описывается прозой — какие возможности от неё ожидаются, а не структурой
компонентов. Это IPageDocument (в 65_Ui/Pages/<Area>/) с:
Description— одно предложение: что это за страница;- один-два прозовых блока (
public static string), например «Возможности» и «Карточка (drawer)» — флоу-текст: что показывает список (грид, поля, поиск, подгрузка), что можно открыть, создать, отредактировать, удалить; какие защиты и какой доступ.
Ссылки — типизированные (SdaidAnchors.RefTo<...>()): на endpoints-источники данных и
команды, на доменные сущности, на другие страницы. Клик ведёт на их страницы,
переименование ловит компилятор. Голую прозу-имя без ссылки не оставляем.
Список и его карточка/форма (drawer/modal) живут в одном документе страницы, пока
близки по смыслу. Если форма вырастает в модалку или отдельную страницу редактирования —
выделяется в отдельный IPageDocument.
17.1. Что НЕ пишем
- Структуру компонентов — никаких именованных свойств-кнопок, блоков колонок,
dynamic-полей, таблиц «Name/Required/Sends». Это шум; нужное говорится прозой. - Размещение в меню/навигации — решение sitemap-а (см. ADR-UI routing), не свойство страницы.
- Точную вёрстку — это HTML-прототип и реализация, не спека-описание.
17.2. Доступ
Доступ к странице и действиям — через UI-claims (rftm/ui/menu, rftm/ui/entity);
серверные операции дополнительно проверяются на backend. В прозе указываем ожидаемый доступ.
17.3. HTML-прототип
Кликабельный макет кладётся в html/ рядом со страницей и встраивается в спеку через
SdaidHtml.IframeOf<TPage>(...) (+ SdaidHtml.LinkTo<TPage>(...) — открыть в новой вкладке).
Прототип берёт стили из единого 65_Ui/html/shared.css (линк
../../../html/shared.css) — не инлайнит свой CSS, чтобы прототипы не «плыли» друг от
друга. Разметка прототипа самодостаточна, общий стиль — один на все.
18. Когда нужен use-case, а когда нет
Use-case-документ создаётся только если выполнено хотя бы одно:
- сценарий проходит через 2+ страницы;
- запускает фоновый процесс или интеграцию;
- задевает 2+ bounded context;
- имеет состояния, сохраняемые между шагами;
- содержит cross-cutting NFR.
Иначе — описание живёт в page-спеке. «Просмотр одного грида + одного query» — не use-case, это функциональность одной страницы.
Use-case — карта пересечений: цель, актор, шаги Given/When/Then на бизнес-уровне (без слов «контроллер», «компонент»), таблица «шаг → page → endpoint → handler → entity», локальные NFR, открытые вопросы. Пересказ page-спеки в use-case — запрещён.
19. State machines
State-machine состоит из трёх частей, и попытка положить всё в один формат теряет одну из них:
- Структура (статусы, допустимые переходы) — таблица.
- Поведение (что делается на переходе) — проза.
- Архитектурная привязка (где исполняется) — короткий блок.
В новой методологии state-machine — это C#-класс с маркером IStateMachineDocument, в Body секций которого:
- Перечень статусов берётся из enum (
IDomainEntityDocumentна enum-сущности в50_Domain/<BC>/) — форма уже зафиксирована в коде. - Mermaid
stateDiagram-v2для визуализации (в Body секции). - Таблица допустимых переходов с триггерами (в Body секции).
- Проза по поведению на каждом переходе (отдельная секция или абзац).
- В конце — где и как исполняется (оркестратор, сервисы).
Файл размещается рядом с use-case-ом, который владеет машиной: 60_Epics/<E>/UseCases/<UC>/StateMachines/<Aggregate>StateMachine.cs. Имена статусов компайл-сейфтятся через nameof() на enum.
20. Именование
- Все классы — PascalCase:
ImportJobsListPage.cs,CreateImportJobUseCase.cs. - Use-case — PascalCase, имя класса говорит о действии:
CreateImportJob,VerifyInformationMap. - State-machine — PascalCase с суффиксом
StateMachine(илиLifecycleдля не-статусных машин):ImportJobStateMachine. - Page artifacts (три ссылки):
- UiSpec (требование):
<Name>UiSpec.cs— напримерImportJobsListUiSpec. - Прототип (HTML):
<Name>.htmlрядом со спекой в80_Subsystems/Ui/Prototypes/. - Page implementation:
<Name>Page.cs(+ опц.<Name>Page.md) в80_Subsystems/Ui/Pages/.
- UiSpec (требование):
- Имена не дублируют контекст папки: внутри
InformationMapImport/пишемImportJobsListPage, а неInformationMapImportJobsListPage. - UI-реализация (Angular) ссылается на спеку, не наоборот. Имена файлов в коде Angular могут отличаться от имён файлов спеки.
21. ADR vs adr.md эпика
40_Arch/ADR/— сквозные решения проекта со строгой нумерацией:ADR_<NNN>_<Name>.cs(класс сIAdrDocument). Сюда попадает то, что переиспользуется или влияет на проект целиком.40_Arch/Tech/<Stack>/— техно-практики (Angular, .NET, EF). ТожеIAdrDocument-классы, но привязаны к стеку.60_Epics/<E>/Adr/— локальные решения этого эпика. Классы видаR_N_<Name>.cs(всё ещёIAdrDocument, кодировка локальная — нумерация R-1, R-2 в пределах папки).80_Subsystems/<S>/Adr/— ADR деплой-единицы.
Если решение в эпик-локальном Adr начинает повторяться в других эпиках — поднимаем его в 40_Arch/ADR/ отдельной задачей.
22. Открытые вопросы
Все нерешённые вопросы эпика живут в одном месте — секция «Открытые вопросы» в IEpicDocument-классе эпика (60_Epics/<E>/-readme). Use-cases, UI-spec-и, handler-spec-и и state-machine-ы на эти вопросы ссылаются через nameof() на конкретные секции (OQ-секции эпика — каждая отдельный ISdaidDocumentSection-класс, тогда auto-linkify работает).
Это запрещает спеке выглядеть «готовой», когда в ней дыры.
23. Acceptance criteria
AC живут на двух уровнях:
60_Epics/<E>/Acceptance.cs(или секции внутри эпик-readme) — cross-cutting AC всего эпика (системно-важные).60_Epics/<E>/UseCases/<UC>/Acceptance.cs— локальные AC use-case-а.
Главное представление — сводный список всех AC по проекту со статусами (генерится автоматически по kind-index-у или через faceted view).
Правила:
- Только пользователь-наблюдаемое поведение. Внутренние инварианты бэка (защита запрещённых переходов state-machine, оптимистические блокировки, конкурентность) описаны в state-machine и реализуются автоматически — не выносить в AC.
- Измеримость. Каждое AC — потенциальный e2e-тест или ручной чеклист.
- Префикс по группе для удобства ссылок: AC-C* (создание), AC-L* (список), AC-D* (детали), AC-S* (lifecycle, наблюдаемая часть), AC-X* (consistency данных), AC-N* (NFR). Состав групп — на усмотрение эпика.
- AC, заблокированные открытыми вопросами, помечаются ссылкой на соответствующий OQ через
nameof().
24. Карта реализации (traceability)
Traceability в новой методологии автоматическая — строится генератором из nameof()-упоминаний в прозе. Не нужно вручную поддерживать таблицу «Артефакт → Где»; mention-graph её собирает.
Use-case остаётся самостоятельным документом (IUseCaseDocument) потому что:
- Given/When/Then narrative нельзя «выжать» из ссылок на artifact-ы;
- локальные NFR и AC use-case-а — их собственные секции;
- открытые вопросы UC ссылаются на конкретные шаги.
На странице эпика и use-case-а генератор автоматически рендерит блок «Связанные артефакты» (все классы, упомянутые через nameof()) и «Где используется» (обратные ссылки).
25. Как работать с спекой
25.1. Разработчику
- При работе с реализацией в
src-api/<X>илиsrc-ui/<X>— открой соответствующий артефакт спеки в рендеренном сайте; navigation от него к связанным (handler ↔ service ↔ state machine ↔ use-case) делается через mention-graph. - При работе с эпиком — начинай с
60_Epics/<E>/-readme (IEpicDocument), оттуда — по auto-linkified ссылкам. - При расхождении spec ↔ impl — фиксируй вопрос явно. Не приводи код к спеке и не правь спеку под код молча.
- Изменил реализацию → проверь спеку соответствующего use-case-а/handler-а/service-а. Не уверен — добавь OQ-секцию в эпике.
25.2. ИИ-ассистенту
- Перед редактированием кода в зоне эпика — загрузи
IEpicDocument-readme эпика, связанныеIUseCaseDocument/IHandlerDocument/IDomainServiceDocument/IStateMachineDocument. - Перед редактированием спеки — проверь существующую реализацию в
src-api/илиsrc-ui/. - Не проводить тихую синхронизацию spec ↔ impl: при расхождении явно сообщить пользователю, спросить, что — источник правды.
- При работе с HTML-прототипами UI: cs-спека (
IUiSpecDocument) важнее прототипа; прототип после реализации компонента не синхронизировать автоматически. Подробнее — секция HTML-прототипы.
25.3. Сборка спеки
dotnet build sdaid/Ban.Sdaid.Icd/Ban.Sdaid.Icd.csproj
Проект — это сам SDAID-спека на C#. Компилируется как линтер: переименование сущности через F2 правит все её упоминания, find-references показывает зависимости, ссылки на несуществующие классы (через nameof(), RefTo<T> и т. п.) ломают сборку.
После любого обновления методологии (и спеки в целом) — обязательно перегенерировать HTML-сайт вызовом sdaid\build-html.cmd. Сборка проверяет компайл-сейфти, рендер — что секции выглядят как ожидалось; одно без другого оставляет рассинхрон.
26. Общие антипаттерны
Базовый список того, что не нужно делать при ведении спеки. Не привязан к структуре папок — антипаттерны процесса/мышления.
- Не плодить use-case-ы, когда хватает page-спеки. Если сценарий = «открыл одну страницу, нажал кнопку, увидел результат» — это поведение страницы, не use-case. Use-case оправдан только при cross-page потоке, фоновом процессе, multi-bounded-context взаимодействии или сохранённом состоянии между шагами.
- Не выносить внутренние инварианты бэка в acceptance. AC — это наблюдаемое пользователем поведение. Защита запрещённых переходов state-machine, оптимистическая блокировка, конкурентность — описаны в state-machine и реализуются автоматически. В AC они дублируют contracts.
- Не нумеровать локальные решения эпика в общем ADR-индексе. Локальные R-N эпика — в
Epics/<E>/Adr/, со своей нумерацией. Если решение начало повторяться в других эпиках — поднимаем в общий40_Arch/ADR/отдельной задачей. - Не редактировать вручную сгенерированные файлы. Если такие появятся (rendered HTML, генерируемый код) — правка идёт через источник, не через output. Иначе drift гарантирован.
27. Журнал изменений методологии
Принятые изменения самой методологии — для тех, кто читал её раньше и хочет увидеть, что
поменялось, не перечитывая документ целиком. Новые записи — сверху; дата — когда решение
принято. Это журнал решений по методике (не путать с CHANGELOG нотации
Ban.Sdaid.Notation, который описывает API авторской нотации).
27.1. 2026-08-03
- Заведено. Методология перенесена в проект ICD как основа заготовки спецификации. Секция «Историческое (только для миграции)» и обсолетный список «Что НЕ нужно делать» не переносились — это история конкретного проекта-донора, к ICD отношения не имеет.