Когда разработчик заходит в незнакомый сервис, первый вопрос — «что он вообще делает?». Ответ обычно приходится собирать по кусочкам: читать код, смотреть в базу, расспрашивать коллег. Это теряет время и приводит к ошибкам.
Спецификация — единый документ, который отвечает на этот вопрос. Описывает один Bounded Context в доменных терминах: что умеет сервис, какие у него роли, какие события он публикует, как устроен жизненный цикл сущностей. Бизнес-аналитик, архитектор и разработчик заполняют её совместно; AI-агент использует как источник памяти и генерирует по ней код.
Почему не просто README
README описывает «как запустить». Спецификация описывает «что это такое и почему так устроено».
Без спеки одна и та же вещь называется по-разному в коде, в задачах и на созвонах. Новый разработчик не знает, где граница сервиса — что он должен делать, а что нет. AI-агент не может сгенерировать корректный код, не зная бизнес-правил.
Спецификация решает три проблемы:
- фиксирует единый язык — одни термины в коде, документах и разговорах;
- задаёт границу сервиса — что внутри, что снаружи, что на стыке;
- становится источником для генерации кода, тестов и диаграмм.
Принципы формата
Прежде чем смотреть на структуру, важно понять несколько правил, на которых она держится.
Единица спеки — Bounded Context, не агрегат. Один контекст = одна спека. Если в контексте несколько агрегатов, спека разбивается на корневой файл (секции уровня контекста) и по файлу на агрегат. Разбивать один контекст на несколько спек нельзя: Ubiquitous Language, Context Map, роли — свойства всего контекста.
Домен без техники. Все секции, кроме «Технической реализации», пишутся в доменных терминах. Фреймворки, аннотации, схема базы данных, топики, стек — только в разделе «Техническая реализация». Так спека остаётся понятной бизнесу и не устаревает при смене технологий.
Ничего не дублируется. Каждый факт — в одном месте; остальные ссылаются. События описываются у агрегата-владельца; переходы — в матрице жизненного цикла; ошибки — в командах и бизнес-правилах.
Карточки + строгие таблицы. Команды, запросы, use cases — карточки ### Имя с размеченными полями: читаемо человеку, парсится агентом. Структурные секции (доступ, события, интеграции) — таблицы.
Когда факт нужен двоим
«Ничего не дублируется» — принцип, который ломается на первом же настоящем конфликте: событие
OrderConfirmed важно и агрегату-владельцу, который его публикует, и подписчику в другом контексте,
для которого это вход. Кто его описывает?
Ответ: описывает владелец, ссылается потребитель. Полное описание события — поля, триггер, область видимости — живёт у агрегата, который его порождает. У подписчика в его спеке стоит строка в таблице интеграций: какое событие он слушает, из какого контекста, что делает в ответ. Это не дубль, потому что описываются разные вещи: там — что случилось, здесь — что мы на это делаем.
| Факт | Полное описание | Где ссылка |
|---|---|---|
| Публикуемое событие | у агрегата-владельца, в «Доменных событиях» | у подписчика — в «Интеграциях», строкой «слушаем X из контекста Y» |
| Чужое событие, на которое реагируем | в «Интеграциях» нашего контекста | в матрице переходов агрегата — как триггер перехода |
| Статус и переход | в матрице переходов агрегата | в «UI-спецификации» — как это видит пользователь |
| Бизнес-правило | в «Бизнес-правилах» агрегата, с кодом | в карточке команды — по коду, а не текстом |
| Термин | в «Ubiquitous Language» корня | везде — просто используется |
| Роль | в «Роли и доступ» корня | в матрице доступа агрегата — как столбец |
Три правила, которые разрешают почти все спорные случаи:
- Дублируется текст, не ссылка. Написать «BR-O01» в карточке команды — это ссылка. Переписать формулировку правила в карточку — дубль, который разойдётся.
- У каждого факта один автор и много читателей. Если два человека правят одно место — граница проведена неверно, и это сигнал не про формат, а про владение контекстом.
- Когда владелец неочевиден, владельцем становится тот, кто меняет факт. Событие меняет тот, кто его публикует; формат ответа меняет тот, кто отвечает. Правило «владеет тот, кто меняет» снимает большинство споров о том, где описывать.
Отдельный случай — контракт между контекстами. Схема события, которую читают чужие сервисы, одновременно часть спеки владельца и обязательство перед потребителями. Дубля здесь тоже нет: в спеке живёт смысл и поля, в контракте событий — схема, и они связаны гейтом сверки. Кто хозяин имени поля в этой связке — в разделе ниже.
Раскладка файлов
Спека живёт в docs/spec/ и всегда делится на два уровня:
docs/spec/
<service>-spec.md # корень: секции уровня контекста
aggregates/
<aggregate>.md # по файлу на агрегат: секции уровня агрегата
Агрегат — всегда в отдельном файле aggregates/<name>.md, даже если он один. На Уровне 1–2, где DDD-агрегатов ещё нет, в этот файл кладут центральную сущность контекста (product.md, notification.md). Структура «корень + aggregates/» единая во всех спеках.
В начало каждого файла добавляют минимальный frontmatter для идентификации чанка при индексации. Для корня — context, bounded-context, level. Для файла агрегата — context, aggregate, level.
Поля context и bounded-context выглядят дублем, но это разные вещи. context — техническое имя, одно и то же в корне и во всех файлах агрегатов: по нему файлы собираются в одну спеку (обычно это имя сервиса — catalog, cabinet). bounded-context — имя контекста на языке домена, как его называют бизнес и глоссарий (Каталог товаров, Bus Ticket Sales); стоит только в корне.
Слева раздел спеки, дальше его запись и артефакты, которые из неё выводятся: по этой цепочке код и сверяют со спекой.
Что входит в корневой файл
Корневой файл описывает контекст в целом. Он состоит из одиннадцати разделов:
| № | Раздел | Что внутри |
|---|---|---|
| 1 | Bounded Context | миссия, субдомен (Core/Supporting/Generic), владелец, таблица агрегатов, что внутри/вне границы, стыки |
| 2 | Интеграции (Context Map) | карта контекстов (mermaid) + таблица рёбер + ссылки на контракты (OpenAPI/AsyncAPI) |
| 3 | Ubiquitous Language | глоссарий контекста; «не путать с» |
| 4 | Роли и доступ | роли, общие ABAC-правила, PII; доступ к операциям — в каждом агрегате |
| 5 | Доменные события | контракт публикуемого языка: агрегат → внешние события → топик |
| 6 | Use Cases | сквозные сценарии карточками ### UC-N |
| 7 | Процессы | Saga/Process Manager: кросс-агрегатные процессы, sequence-диаграммы |
| 8 | UI-спецификация | связь статусов с UI, тексты ошибок для пользователя |
| 9 | Критерии приёмки | Given / When / Then |
| 10 | Нефункциональные требования | производительность, доступность, согласованность, безопасность, наблюдаемость — целевые значения без инструментов |
| 11 | Техническая реализация | единственный технический раздел: контейнеры C2, стек, схема БД (ER + индексы) |
Что входит в файл агрегата
Каждый агрегат описывается в семи разделах:
| № | Раздел | Что внутри |
|---|---|---|
| 1 | Доменная модель | агрегат-корень + сущности, value objects (через инвариант), class-диаграмма |
| 2 | Жизненный цикл | статусы + матрица переходов (команды, события, политики/таймауты) + state-диаграмма |
| 3 | Доступ | матрица «операция × роль» + ABAC |
| 4 | Бизнес-правила | список BR-<префикс>NN с типом, командой и кодом ошибки |
| 5 | Команды | карточки: Переход · Вход · Предусловия · Логика · Эмитит · Ошибки |
| 6 | Доменные события | таблица «событие — триггер — scope — подписчики» |
| 7 | Запросы | карточки: Вопрос · Параметры · Возвращает · Логика (источник чтения, фильтр, согласованность) |
Как это выглядит заполненным
Таблицы выше описывают, что должно быть в разделах. Ниже — как это выглядит на самом деле, на трёх самых важных артефактах: карточке команды, матрице переходов и бизнес-правилах.
Карточка команды
### Команда: CancelOrder
Переход: DRAFT|CONFIRMED → CANCELLED
Вход: orderId, reason, idempotencyKey
Предусловия: заказ существует; актор владелец заказа или роль operator;
заказ не отгружен
Логика: проверить BR-O05 (отмена невозможна после отгрузки);
освободить резерв позиций; если заказ был оплачен — пометить к возврату;
перевести в CANCELLED, записать причину
Эмитит: OrderCancelled { orderId, reason, refundRequired, cancelledAt }
Ошибки: ORDER_NOT_FOUND, ORDER_ALREADY_SHIPPED (BR-O05),
ORDER_ALREADY_CANCELLED, FORBIDDEN
Что делает эту карточку рабочей, а не описательной: каждая строка превращается в артефакт. «Вход» — в поля команды, «Переход» — в проверку статуса, «Логика» — в порядок вызовов у обработчика, «Ошибки» — в классы исключений и в ответы контракта, «Эмитит» — в схему события. По этому же списку собирается перечень тестов. Строка «Предусловия» отличается от «Логики» тем, что проверяется до изменения состояния и обычно относится к правам и существованию, а не к бизнес-правилам.
Матрица переходов
Артефакт, который упоминается чаще всех и вызывает больше всего вопросов. Это таблица жизненного цикла: из какого статуса, в какой, по какой команде или событию, при каком условии.
## Жизненный цикл
Статусы: DRAFT, CONFIRMED, PAID, SHIPPED, DELIVERED, CANCELLED
| Из | В | Триггер | Тип | Условие / политика |
|---|---|---|---|---|
| — | DRAFT | CreateOrder | команда | — |
| DRAFT | CONFIRMED | ConfirmOrder | команда | BR-O01, BR-O03 |
| DRAFT | CANCELLED | CancelOrder | команда | — |
| CONFIRMED | PAID | PaymentCaptured | событие | событие из контекста платежей |
| CONFIRMED | CANCELLED | таймаут оплаты | политика | 15 минут без оплаты, планировщик |
| CONFIRMED | CANCELLED | CancelOrder | команда | BR-O05 |
| PAID | SHIPPED | ShipOrder | команда | роль operator |
| SHIPPED | DELIVERED | DeliveryConfirmed | событие | событие из контекста доставки |
| CANCELLED | — | — | — | терминальный, переходов нет |
Колонки объясняются так:
- Из / В — статусы. Прочерк в «Из» означает создание, прочерк в «В» — терминальное состояние.
- Триггер — что вызывает переход: имя команды, имя события или описание политики.
- Тип — команда (кто-то попросил), событие (что-то случилось снаружи), политика (сработало по времени или по правилу без внешнего запроса). Это различение и есть ответ на вопрос, что значит «политики/таймауты»: политика — переход, который делает сама система, без команды и без внешнего события, обычно по истечении срока.
- Условие / политика — коды бизнес-правил, требуемая роль, срок таймаута, источник события.
Почему матрица важнее списка статусов: она перечисляет и то, чего нет. Пара, которой в таблице нет, — запрещённый переход, и это проверяемое утверждение: перечисление в коде и тест на запрещённые переходы генерируются прямо из неё. Отсюда и практическое правило заполнения: сначала выписать все статусы, потом все разрешённые пары, а всё остальное считать запрещённым по умолчанию.
Бизнес-правила и доступ
## Бизнес-правила
| Код | Правило | Тип | Команда | Ошибка |
|---|---|---|---|---|
| BR-O01 | В заказе хотя бы одна позиция | инвариант | ConfirmOrder | ORDER_EMPTY |
| BR-O03 | Все позиции в наличии на момент подтверждения | предусловие | ConfirmOrder | ITEM_UNAVAILABLE |
| BR-O05 | Отмена невозможна после отгрузки | инвариант | CancelOrder | ORDER_ALREADY_SHIPPED |
| BR-O07 | Сумма заказа равна сумме позиций со скидкой | инвариант | все | — |
## Доступ
| Операция | customer | operator | support | Условие (ABAC) |
|---|---|---|---|---|
| CreateOrder | да | нет | нет | — |
| ConfirmOrder | да | да | нет | customer только свой заказ |
| CancelOrder | да | да | нет | customer только свой и до отгрузки |
| ShipOrder | нет | да | нет | — |
| GetOrder | да | да | да | customer только свой |
Различие «инвариант / предусловие» здесь не формальность: инвариант обязан держаться всегда и проверяется внутри объекта при любом изменении; предусловие проверяется на входе в операцию и может опираться на внешние данные (наличие на складе). Первое живёт в модели, второе — в обработчике, и именно это различие определяет, где ревью ожидает увидеть проверку.
Правило без ошибки (BR-O07) — это инвариант, который нельзя нарушить снаружи: он поддерживается
конструкцией, а не проверкой, и потому кода ошибки у него нет.
Несколько важных конвенций
Value Objects описываются через инвариант. Value Object — объект, который узнают по значению, а не по идентификатору (деньги, адрес, период), а инвариант — правило, которое он обязан держать всегда; подробнее в тактических паттернах DDD. Описывают его не через поля и типы (это зона базы данных), а через то, что он защищает. Для sum-типов — варианты: Discount: Percentage | Fixed.
Бизнес-правила кодируются с префиксом агрегата. BR-O01, BR-D01 — чтобы коды не пересекались между агрегатами.
Команды в «Командах» — только публичные. Реакции на события и политики (таймауты) — не отдельные карточки. Они уже отражены в матрице переходов и «Доменных событиях».
Каталога ошибок нет. Коды ошибок — в «Командах» (поле «Ошибки») и «Бизнес-правилах»; тексты для пользователя — в «UI»; HTTP-маппинг — артефакт API, не спеки.
Кросс-агрегатные ссылки — по ID, без внешних ключей. <other>_id — логическая ссылка; это отражается и в class-диаграммах, и в ER.
Уровень зрелости — сколько заполнять
Спека не требует одинаковой глубины для всех сервисов. Глубина определяется уровнем зрелости сервиса:
| Уровень | Архитектура | Что меняется в спеке |
|---|---|---|
| 0 — As-is | реверс из кода | снимок «как есть»; not-declared для пробелов; точка отсчёта для миграции |
| 1 — Слоёный | Controller → Service → Repository | DDD-агрегатов нет (один «модуль»); «Доменная модель» = ER + таблицы; «Доменные события» и «Процессы» пропускаются с пометкой |
| 2 — Use Case Pattern | UseCase + Handler | команды = UseCase-классы; события — если реально публикуются; CQRS + Read Model — опция уровня |
| 3 — DDD + Hexagonal | агрегаты, доменные события, ports/adapters | полная глубина, разбивка по агрегатам |
Раздел, неприменимый на текущем уровне, не пропускают молча — пишут заголовок и однострочную пометку: «Не применимо на Уровне 1».
Выглядит это буквально так — заголовок на месте, под ним одна строка:
## 5. Доменные события
Не применимо на Уровне 1: сервис не публикует события, интеграция только синхронная.
## 7. Процессы
Не применимо на Уровне 1: кросс-агрегатных процессов нет.
Почему именно так, а не просто пропустить: пропущенный раздел неотличим от забытого. Читатель (и агент) видит спеку без раздела «Доменные события» и не знает, события не публикуются или их не описали. Пометка отвечает на это за одну строку и заодно объясняет причину — а причина превращается в признак перехода на следующий уровень: как только события появятся, строка станет неправдой, и это заметят.
Формулировка пометки содержит две вещи: уровень и почему. «Не применимо» без причины — та же неопределённость, только с заголовком.
Сколько это страниц
Чтобы понимать, во что ввязываешься, полезны ориентиры по объёму. Порядок величины для одного контекста среднего размера:
| Уровень | Корневой файл | Файл агрегата | Всего | Сколько занимает заполнение |
|---|---|---|---|---|
| 0 — as-is | 2–4 страницы, половина not-declared | 1–2 страницы | 4–6 страниц | часы генерации плюс день вычитки |
| 1 — слоёный | 3–5 страниц, три раздела помечены | 1–2 страницы на сущность | 5–8 страниц | день-два вместе с бизнесом |
| 2 — Use Case Pattern | 5–8 страниц | 2–4 страницы | 8–15 страниц | два-три дня |
| 3 — DDD и Hexagonal | 8–12 страниц | 4–8 страниц на агрегат | 15–30 страниц на контекст с двумя-тремя агрегатами | неделя старшего инженера с бизнесом |
Что важнее самих чисел — признаки неправильного объёма:
- Спека уровня 3 короче десяти страниц почти всегда означает, что матрицы переходов и бизнес-правила заполнены формально: «статусы есть, переходы очевидны». Очевидных переходов не бывает.
- Спека длиннее сорока страниц на один контекст означает одно из двух: в неё утекла техника (схемы, конфигурация, примеры кода) или контекст на самом деле два.
- Файл агрегата длиннее десяти страниц — сигнал, что агрегат слишком большой; это разговор о границах агрегата, а не о формате.
- Корневой файл длиннее файлов агрегатов вместе — обычно значит, что в корень уехало то, что относится к агрегатам: команды, правила, доступ.
И оговорка про страницы как меру: считать надо не символы, а заполненные разделы. Спека на шесть страниц со всеми одиннадцатью разделами лучше двадцати страниц, где четыре раздела расписаны, а семь отсутствуют.
Уровень 0 — as-is из кода. Это особый режим: спеку по существующему сервису без бизнес-брифа генерирует скилл ucp-spec-tier-0, читая код, миграции и конфигурацию. Он ставит level: 0, не выдумывает агрегаты там, где код без доменной логики, отсутствующие данные помечает литералом not-declared. Это точка отсчёта для перехода на нужный уровень.
Кто обновляет спеку и что делать при расхождении
Формат описан, а правила ведения — нет, хотя именно на них спека и живёт или умирает.
Обновляет тот, кто меняет поведение. Не аналитик «потом», не отдельная роль хранителя документации. Изменили команду в коде — карточка в спеке правится тем же человеком и в том же изменении. Это единственный порядок, который работает: любая отложенная синхронизация не происходит.
Изменение начинается со спеки, а не с кода. Правка поведения — это сначала правка карточки (или отдельный документ изменения, если контекст большой и правка ломающая), потом код. Причина та же, что у теста до кода: агент получает обновлённую карточку и делает по ней, а приёмка идёт по ней же. Правка в два поля тоже проходит через спеку — именно мелкие правки её и убивают.
Кто утверждает. Зависит от того, что меняется:
| Что меняется | Кто решает |
|---|---|
| Формулировка, опечатка, уточнение без смены смысла | автор, без обсуждения |
| Новая команда или запрос внутри контекста | автор плюс ревью команды |
| Бизнес-правило, статус, переход | вместе с владельцем домена: это решение о продукте |
| Публикуемое событие или контракт наружу | согласование с потребителями, потому что это обязательство |
| Границы контекста, состав агрегатов | архитектурное решение с записью причины |
Расхождение спеки и кода — дефект, а не долг. Нашли на ревью: чинится в том же изменении, и чинится одно из двух — либо код под спеку, либо спека под код с записанной причиной. Формулировка «поправим спеку следующим изменением» не принимается по той же причине, по которой не принимается красная сборка: следующего изменения не бывает.
Как расхождение обнаруживается — три слоя, от машинного к человеческому:
- Гейты в сборке сверяют формальное: команды из карточек против классов, коды ошибок против исключений, события против схем контракта, статусы из матрицы против перечисления в коде. Это ловит большинство расхождений, потому что типичное расхождение и есть «добавили операцию, карточку не тронули».
- Ревью по карточке. Смысловое расхождение (логика в обработчике не совпадает со строкой «Логика», правило проверяется не там, где положено) машина не видит. Поэтому на ревью карточка и код смотрят рядом, а не только код.
- Периодическая сверка. Раз в несколько недель прочитать спеку и код целиком и выписать расхождения по смыслу. Результат — задачи с приоритетом, а не заметки.
Что делать, если спека уже отстала сильно. Не переписывать целиком: сгенерировать снимок «как есть» и сравнить с тем, что было записано. Разница делится на три части — что изменилось осознанно (обновить), что изменилось случайно (это дефекты, завести задачи), чего в коде нет, а в спеке есть (удалить или признать невыполненным обязательством). Порядок разбора именно такой: вторая часть — самое ценное, что даёт эта операция.
Глубже: договор между спекой и контрактом API: кто хозяин имени полярасширенное
«Из Commands получается OpenAPI, из Domain Events AsyncAPI» требует правил соответствия, иначе через месяц в контракте поле customer_id, в спеке «покупатель», в коде buyerId, и никто не знает, кто прав.
Хозяин имени это спека. Термин из Ubiquitous Language контекста определяет имя поля во всех артефактах: в карточке команды, в схеме OpenAPI, в событии, в коде и в таблице; перевод с русского термина на английский идентификатор один и записан в глоссарии, о чём статья про стратегические паттерны DDD. Контракт API не придумывает имён, он транслирует.
Правила соответствия. Карточка команды даёт операцию: имя операции равно имени команды, поля «Входа» становятся телом или параметрами, «Ошибки» становятся ответами с кодами (сопоставление кода ошибки со статусом это артефакт API, и оно живёт в одном месте, а не в спеке), «Переход» задаёт, какие статусы ресурса операция принимает. Карточка запроса даёт GET с параметрами из «Параметров» и схемой из «Возвращает». Событие из «Доменных событий» даёт схему сообщения в AsyncAPI с теми же полями и именем топика из таблицы. Всё, чего нет в карточке, в контракте появиться не может: поле «для удобства фронта» сначала попадает в спеку.
Когда контракт меняется, а спека нет. Это расхождение, и его ловит гейт сверки операций и полей; чинят спеку первой (документ изменения с классификацией: совместимое или ломающее), потом контракт. Обратное направление, спека изменилась, контракт нет, ловится тем же гейтом, и здесь ещё проверяют совместимость контракта с предыдущей версией инструментом сравнения спецификаций, о чём статья про API-first.
Что спека не решает. Версионирование URL, формат дат и денег, пагинация, заголовки идемпотентности и трассировки: это соглашения API, общие для всех сервисов, и они живут в стандарте API, а спека на них ссылается. Так у спеки один хозяин имён и смысла, у стандарта хозяин формы, и спор «как назвать поле» решается один раз в глоссарии.
Глубже: сколько стоит методология и когда окупаетсярасширенное
Три статьи требуют инвестиций («неделя старшего инженера», «день-два на сервис», «сотни правил»), и ни одна не складывает их вместе. Сложим по порядку величины.
Заведение. Корпус правил и скиллы берут готовые, установка это часы; настройка под проект (какие правила включить, гейты, базовые списки нарушений) это день-два инженера, который понимает методологию. Спека контекста на уровне 0 генерируется из кода за часы и правится глазами за день; спека уровня 3 для сложного контекста с агрегатами это неделя старшего инженера вместе с бизнесом, и половина этой недели это Event Storming, который окупился бы и без методологии.
Поддержание. Каждое изменение поведения стоит карточку и документ изменения: от десяти минут для поля до часа для новой операции, то есть 5–10 % времени разработки; гейты в сборке бесплатны после заведения; сверка раз в месяц это час. Обучение нового участника команды: неделя до самостоятельной работы по спеке против месяца чтения кода без неё.
Что на другой чаше. Время на выяснение «как это работает» у каждого, кто трогает сервис; переделки после ревью, когда правило нарушили и узнали поздно; регрессии от изменений, которые задели соседнее; и цена работы с агентом без модели, когда каждая сессия изобретает устройство заново, и приёмка становится дороже генерации. Эти статьи расходов обычно 20–40 % времени команды на сервисе старше года, и это измеримо по журналу задач.
Точка окупаемости. Сервис, который живёт больше года, меняется каждую неделю и в котором работает больше одного человека или один человек с агентом: окупается за первые месяцы. Сервис на квартал с одним разработчиком: не окупается, о чём раздел «когда не брать» в статье про уровень 0. Считать это на своём сервисе просто: неделя заведения плюс десять процентов сверху против доли времени, которое сейчас уходит на выяснение, переделки и регрессии; вторая цифра почти всегда больше, и её редко кто считал.
Коротко
- Спека описывает один Bounded Context в доменных терминах; бизнес-правила, роли, события, жизненный цикл. Файлы делятся на корень (уровень контекста) и
aggregates/<name>.md(уровень агрегата); структура единая для всех уровней зрелости. - Всё, что не «Техническая реализация», пишется без упоминания фреймворков, аннотаций и схемы базы данных. Команды, запросы, use cases — карточки
### Имя; структурные секции — нормализованные таблицы. Бизнес-правила кодируются с префиксом агрегата (BR-O01); каждый факт — в одном месте. Глубина спеки = уровень зрелости сервиса (0–3); неприменимые разделы помечаются, не пропускаются. - Хозяин имён это спека через глоссарий: команда даёт операцию, запрос даёт
GET, событие даёт схему сообщения; поле, которого нет в карточке, в контракт не попадает; форма (версии, даты, пагинация) живёт в стандарте API. - Цена методологии: день-два настройки, от часов до недели на спеку контекста, 5–10 % времени на ведение; против 20–40 % на выяснение, переделки и регрессии в сервисе старше года; окупается за месяцы там, где сервис живёт и меняется.
- Факт, нужный двоим, описывает владелец, а потребитель ссылается: событие полностью — у агрегата-владельца, у подписчика строка в интеграциях; владеет тот, кто меняет.
- Карточка команды рабочая, когда каждая её строка превращается в артефакт: вход в поля, переход в проверку статуса, логика в порядок вызовов, ошибки в исключения и ответы, «эмитит» в схему события.
- Матрица переходов — таблица «из, в, триггер, тип, условие»: политика это переход, который система делает сама по времени, а отсутствующая пара означает запрещённый переход и превращается в тест.
- Неприменимый раздел помечают заголовком и строкой с уровнем и причиной: пропущенный раздел неотличим от забытого, а причина потом становится признаком перехода на следующий уровень.
- Объём для контекста среднего размера: 4–6 страниц на уровне 0, 8–15 на уровне 2, 15–30 на уровне 3; мерят заполненные разделы, а спека уровня 3 короче десяти страниц означает формально заполненные матрицы.
- Спеку обновляет тот, кто меняет поведение, и в том же изменении; расхождение — дефект, который чинят сразу, а ловят гейты, ревью по карточке и периодическая сверка.
Что почитать дальше
- Кейс маркетплейса — заполненные примеры спек реальных сервисов.
- Уровни зрелости — чем отличаются уровни 0, 1, 2 и 3.
- Стратегические паттерны DDD — границы контекстов и Context Map.
- Настройка скиллов —
ucp-spec-design(по бизнес-описанию),ucp-spec-tier-0(из кода),ucp-spec-review(ревью).