Контекст
bookingАгрегат
ReservationИнвариант
MUSTПроверка
тестПочему спек становится много
Спека на фичупонятие описано в каждой фиче
12 файлов«Бронь» — в 7
растёт с фичамиСловарь агрегатовпонятие описано один раз
ReservationRoomTimeSlotMember4 записи«Бронь» — в 1
растёт с доменомФича 13→[booking::Reservation]+1 MUST→тест+1Числа условные. Как спеки и изменения раскладывает OpenSpec: папки specs/ и changes/, изменения уходят в архив.
Вывод
В этом примереДвенадцать фич дали двенадцать спек, и бронь описана в семи из них по-разному. В словаре те же фичи дописали строки в четыре записи агрегатов.
В общем случаеЕсли спека привязана к изменению или фиче, файлов становится больше с каждой задачей, а одно понятие расходится по нескольким местам. Если единица — агрегат, число записей ограничено самим доменом, а фича только добавляет в запись требования.
Что сделатьВыпишите понятия, которые встречаются в трёх и больше спеках, и заведите каждому одну запись в словаре. Спеки фич пусть ссылаются на запись, а не пересказывают её.
Уровни требований RFC 2119
СловоЗначениеСилаПроверка
MUSTобязательноавтотестблокируетMUST NOTзапрещеноавтотестблокируетSHOULDпо умолчанию, отступ с причинойревьюпредупреждаетMAYдопустимонетне проверяетсяMUST = SHALL
Слова и их смысл — по RFC 2119. Каждому слову соответствует своя проверка.
Вывод
В этом примереВ записи брони правило «интервалы не пересекаются» стоит как MUST и проверяется тестом, а «подтверждение в тот же канал» — как SHOULD и смотрится на ревью.
В общем случаеСлово требования сразу говорит, чем его проверять и что делать при нарушении. Агент читает MUST как границу, а SHOULD как умолчание, и не путает одно с другим.
Что сделатьПерепишите требования своей спеки с этими четырьмя словами и рядом с каждым MUST укажите тест, который его проверяет.
Карточка агрегата
## [booking::Reservation] Class: aggregate Description: Бронь переговорной на интервал.
1адрес: контекст::имя
Invariants: - MUST: active-брони одной комнаты не пересекаются - MUST: held → confirmed → closed, только вперёд - MUST: held без подтверждения 15 мин → closed - SHOULD: подтверждение в канал создания
2требования → проверки
Commands: HoldRoom, ConfirmReservation,
CancelReservation3единственный способ изменить
Access: CanHoldRoom: [booking::Role::Member] CanCancelReservation: Member (своя), Admin
4кто может
Depends on: [[booking::Room]], [[booking::TimeSlot]]
5связи по id
Spec: [[room-booking]]
6из какой фичи пришло
Пример выдуман. Термины — по Martin Fowler: DDD Aggregate.
Вывод
В этом примереВся бронь — одна карточка: адрес, четыре требования, три команды, права, связи и ссылка на фичу, из которой она появилась.
В общем случаеАгрегат — граница, внутри которой требования всегда соблюдаются, а менять его можно только его командами. Поэтому в карточке рядом стоят и правила, и единственные способы их нарушить.
Что сделатьДля каждого агрегата заведите карточку из шести полей. Если требование нельзя приписать ни одному агрегату, скорее всего, агрегат ещё не назван.
Ограниченные контексты
bookingReservationRoomTimeSlot
billingInvoiceTariff
accessPassLevel
связи между контекстами — только по id
billing::Invoice→ id →booking::Reservationaccess::Pass→ id →booking::Reservation«Уровень»
booking::Room.floorЭтажaccess::LevelДопусктермин = одна записьссылка [[ctx::Name]]совпало слово — разные записисловарь меняется вместе со спекой
Подход — Bounded Context и Ubiquitous Language.
Вывод
В этом примереСчёт и пропуск ссылаются на бронь по id и не знают её устройства. Слово «уровень» в бронях значит этаж, а в доступе — допуск, и это две разные записи.
В общем случаеКонтекст — область, внутри которой у каждого слова один смысл. Когда слово совпало в двух контекстах, заводят два термина с разными подписями, иначе агент перенесёт правило из одного в другой.
Что сделатьРазложите агрегаты по контекстам и найдите слова, которые встречаются в двух. Для каждого такого слова заведите отдельные записи и разные подписи в интерфейсе.