Перейти к основному содержимому

Уровень изложения ТЗ (analyst altitude)

Главное правило стиля для всей документации-постановки PIN (docs/06-services/**, use-cases, сущности, события, сценарии). Определяет уровень абстракции: что писать, что опускать, какой блок когда включать. Остальные правила (algorithm-documentation-rules, cqrs-documentation-rules, api-documentation-rules, entity-documentation-rules, event-documentation-rules, task-specification-rules) и шаблоны 99-templates/* подчиняются этому уровню.

Основной принцип

ТЗ — это постановка задачи от аналитика для аналитиков, разработчиков и тестировщиков, а не описание кода. Документ отвечает на вопросы:

  • что система делает и зачем (бизнес-цель);
  • кто имеет право это делать (актор, permission, scope);
  • какую сущность по каким условиям получить/проверить;
  • что делать при успехе и при каждом отказе (happy path + negative paths);
  • как понять, что реализовано корректно (acceptance criteria, ошибки, наблюдаемость).

Документ не отвечает на вопрос «как это в коде»: имена классов/хендлеров/валидаторов, вызовы репозиториев, монада результата, транзакции SDK, конверты сообщений — это детали реализации, им в ТЗ не место. Пиши так, будто система строится с нуля (greenfield): нет прототипов, нет старого Node-BFF, нет «as-is».

Оставляем — это и есть контракт ТЗ

  • Доменные имена: сущности/агрегаты, поля и их типы/ограничения (в таблицах), enum-значения, статусы и переходы, инварианты (INV-*), коды ошибок + HTTP-статус + условие, HTTP-роуты, имена топиков и событий.
  • Доступ: актор, permission/policy, scope, tenant, record-level, audit — каждый пункт только если применим.
  • Данные: модели запроса и ответа таблицами (поле, тип, обязательность, источник/маппинг, ограничения).
  • Проверяемость: acceptance criteria, таблица ошибок, политика retry/DLQ, сигналы наблюдаемости (logs/metrics/traces), правила совместимости (additive/breaking, unknown enum).

Убираем — это детали реализации и мусор

  • Секцию ## CQRS целиком; имена Command/Query/Handler/Validator; интерфейсы IOperationRequest/ITenantedOperationRequest/IOperationHandler; слово FluentValidation.
  • Вызовы репозиториев и переменные: xxxRepository.FindBy(...), .FindById/.GetById/.Add/.Update/ .Search/.Count, присваивания промежуточных переменных.
  • Монаду и конверты: Result.Fail(CODE)-форму (сам КОД остаётся в таблице ошибок), Result<…>, IUnitOfWork.SaveChanges, RaiseDomainEvent(...), RestApiResponse/RequestTraceModel/PagingResult<T>/ IOperationExecutor, KafkaEventMessageEnvelope.
  • В сущностях: таблицы ### Реализуемые capability-интерфейсы (Talent.Common.*), механику EF owned types, строки RowVersion=xmin/IVersionedEntity, любые As-is (drizzle, Node-BFF) → to-be таблицы.
  • Неприменимые блоки и шаги — опускаем целиком. Никаких не применяется — Query, никаких ## Побочные эффекты со всеми none, никаких пустых строк «нет/none».
  • Мета-ссылки внутри спека: «по [Правилам Документирования Алгоритма]», «Cross-cutting требования — по [Общим Требованиям…]», «реестр «один дом»», «заполнен inline», шапку «Один файл = одна HTTP-операция…». Спек ссылается на контракты (сущности, события, соседние операции), а не на правила авторинга.
  • Brownfield-рамку: as-is/to-be, Node-BFF, absorb-node, drizzle, «поглощает операцию…».
  • Технологические протечки в актёрах: «админка Vite», «admin-система» → «администратор платформы» / «admin API»; orval/swagger/@pin/admin — убрать или обезличить.

Матрица блоков — когда какой блок включать

Включай блок, только когда ему есть что сказать. Пустой блок = удалённый блок.

Тип документаОбязательные блокиУсловные (только если есть содержание)Всегда убрать
API-операция (api/**)H1 · Назначение · Metadata · Актор и доступ · HTTP-контракт · Входные данные · Модель ответа · Алгоритм · Ошибки и failure modes · Совместимость и наблюдаемость · Связанные документыПобочные эффекты (только реальные события/внешние вызовы/проекции/уведомления) · Диаграмма (нетривиальное поведение) · record-level/tenant/audit-строки## CQRS, шапка-boilerplate, конверт RestApiResponse
Consumer (consumers/**)Назначение · Триггер (топики) · Маппинг «событие → эффект» (бизнес-термины) · payload→проекция · Алгоритм · Идемпотентность/version-guard · Retry/DLQ · НаблюдаемостьДиаграмматаблица consumer/command/handler, «Change context: Absorb Node-BFF»
Use-case (use-cases/**)Назначение · Актор · Предусловие · Триггер · Основной поток · Постусловие · Acceptance CriteriaАльтернативные потоки · Ошибки · События (если публикуются) · Диаграммашаги с не применяется, вызовы репозиториев в потоке
Entity (domain/**)Назначение · Классификация (контекст, source of truth, таблица) · Поля · Инварианты · СвязиПереходы состояний (если это state machine) · Enum · События · JSONB-модели · Индексы/хранение/безопасностьcapability-интерфейсы, As-is → to-be, EF owned-types механика
Event (events/**)Назначение · Metadata · Payload · Когда публикуется · Потребители · Версионирование/совместимостьБезопасность/PII · Вложенные моделиконверт KafkaEventMessageEnvelope, : IKafkaEvent в заголовке, не применяется unknown-enum
Background job (background/**)Назначение · Расписание и locking · Вход · Алгоритм · Идемпотентность/retry · НаблюдаемостьДиаграмма · Связанный runbookCQRS request/handler/validator, CancellationToken, имена шины
Saga / search / system-scenarioпо своим шаблонам (уже на целевом уровне)остаточные мета-ссылки и SDK-имена

Как писать «Назначение»

2–4 предложения простым языком: бизнес-цель операции, кто её вызывает, что получает на выходе. Без ссылок на ТЗ.md:NNN, без мета-ссылок на правила, без деталей реализации. Читатель за 10 секунд должен понять, зачем этот эндпоинт/сущность существует.

  • Плохо: «Возвращает admin-системе (таб «Корпуса» админки Vite) картину… Поглощает as-is операцию Node-BFF adminGetOverride. Cross-cutting требования — по [Общим Требованиям…]».
  • Хорошо: «Отдаёт администратору платформы карточку витринного оверрайда корпуса: исходные значения из источника, текущий оверрайд и эффективные значения витрины — чтобы решить, какие поля переопределить».

Как писать «Предусловие»

Короткий список бизнес-условий, которые должны выполняться до сценария (состояние актора, статус сущности, наличие данных). Не валидация входа (это в Алгоритме/Ошибках), а именно контекст запуска.

  • Пример: «Пользователь авторизован. Объект опубликован (Status = Published). Метрики аналитики синхронизированы (иначе — деградация: блок метрик скрыт)».

Как писать «Алгоритм»

Нумерованные бизнес-шаги: happy path + negative paths. Для каждого шага — какую сущность по каким условиям получаем и что решаем; при отказе — какой код ошибки возвращаем (коды дублируются в таблице «Ошибки»). Без CancellationToken, без вызовов репозиториев, без Result.Fail(...)-формы, без транзакций SDK. Тривиальный CRUD описывается одним абзацем без нумерации.

Пример шага: «Найти правило ранжирования по цели (тип + id). Если активное правило уже есть → отказ CATALOG_RANKING_RULE_DUPLICATE. Иначе создать правило со статусом Active и в одном согласованном изменении пересчитать приоритет затронутых карточек».

Диаграммы

Mermaid оставляем там, где он проясняет поток, но участники — бизнес/доменные роли (актор, сервис, витрина, внешняя система), а не IDbRepository/IUnitOfWork/Handler/Outbox. Для тривиального CRUD диаграмму не рисуем.

Пример «до/после» (фрагмент алгоритма)

  • До (кодовый уровень): 2. **Валидация.** FluentValidation: TargetId != Guid.Empty → ValidationError. targetProject = projectCardRepository.FindById(TargetId); null → Result.Fail(CATALOG_RANKING_TARGET_NOT_FOUND).
  • После (аналитический уровень): 2. Проверить вход: тип цели ∈ {Проект, Объект}, приоритет в диапазоне 0..1000. Убедиться, что цель существует, принадлежит компании застройщика и не снята с публикации; иначе — ошибка (не найдена / не принадлежит / уже есть правило).

Полнота и глубина спека (analyst altitude, но исчерпывающе)

Уровень абстракции — аналитический, но спек должен быть достаточным для реализации и интеграции без домысливания. Аналитик пишет для другого аналитика/разработчика: из спека должно быть однозначно понятно что построить, в каком порядке взаимодействуют части и как проверить. Углубляем — добавляя смысл, не код. «Глубже» ≠ «ближе к коду».

Углубляем (добавляем, если релевантно типу документа):

  • Исчерпывающие потоки. Happy path + все значимые negative/alternative paths: граничные значения, пустые/предельные входы, конкурентные изменения, повторные вызовы, частичный отказ зависимостей, просроченные/отозванные права. Каждый шаг: какую сущность по каким условиям берём, что решаем, к какому исходу (успех / конкретный код ошибки) ведём.
  • Интеграционные последовательности. Явно: кто кому что шлёт и в каком порядке — синхронные вызовы соседних сервисов и событийные цепочки (какое событие публикуется, кто потребляет, что делает, какое следствие-событие). Отметить синхронно/асинхронно, порядок, окно eventual-consistency, что видит пользователь в промежуточном состоянии.
  • Кросс-сервисные контракты. Что этот сервис ожидает от других (входные события/поля/гарантии) и что предоставляет (исходящие события/поля/гарантии, ключ партиционирования, версия). Явные предположения о данных из реплик и поведение при их отсутствии/устаревании.
  • Предусловия и постусловия. Что должно быть истинно до сценария и что гарантированно истинно после (какие сущности/статусы/счётчики изменились, какие события ушли).
  • Инварианты, статусы, валидации — полностью перечислены. Все INV-*, все переходы статусов (из/в, условие, кто инициирует), все правила валидации полей (границы, форматы, взаимозависимости).
  • Отказы и восстановление как бизнес-правила. Идемпотентность (ключ, поведение при повторе), retry/DLQ (что ретраится, сколько, что в DLQ), version-guard/конкурентность (кто побеждает, что видит проигравший), компенсации/саги при многошаговых эффектах.
  • Acceptance criteria в форме Given/When/Then, покрывающие и happy path, и ключевые edge-cases и отказы.
  • Наблюдаемость и НФТ по бизнес-смыслу. Что логировать/мерить (без секретов/PII), ключевые метрики и пороги, целевая нагрузка/латентность (envelope), требования безопасности/доступа на операцию.

Не добавляем (это НЕ углубление): код, имена классов/методов/хендлеров, вызовы репозиториев, SDK-типы, DDL таблиц, псевдокод «как код», mermaid с инфраструктурными участниками. Список «Убираем» выше остаётся в силе — углубление никогда не возвращает пламбинг.

Чек-лист «спек полон» по типам:

  • API-операция: контракт (роут/вход/ответ) + актор/доступ + исчерпывающий алгоритм (happy + negative) + все коды ошибок с условиями + побочные эффекты (события/проекции/уведомления) + интеграция с соседями + acceptance + наблюдаемость + совместимость.
  • Use-case: предусловие + триггер + основной поток + все альтернативные/ошибочные потоки + постусловие + межсервисная последовательность (события/вызовы) + acceptance (Given/When/Then).
  • Entity: все поля с типами/ограничениями + все INV-* + полная state-machine (если есть) + связи + какие события порождает и при каком изменении + правила видимости/PII.
  • Event: payload (все поля) + точное условие публикации + все потребители и их реакция + гарантии доставки/порядка/идемпотентности + версионирование/совместимость (unknown enum, additive/breaking).
  • Consumer: триггер-топики + маппинг «событие → эффект» + идемпотентность/version-guard + retry/DLQ + что при неизвестном/устаревшем событии + наблюдаемость.
  • Background job: расписание/locking + вход + алгоритм + идемпотентность/retry + что при частичном прогоне/падении + наблюдаемость.

Дополняем существующий контракт, не переписывая и не теряя уже задокументированное (коды ошибок, INV-*, enum, статусы, топики, события сохраняются). Если блок уже полон — не раздуваем ради объёма.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы