Исполняемые спецификации на flang DDD: bounded context, ubiquitous language и command guard
0%

DDD: bounded context, ubiquitous language и command guard

DDD: bounded context, ubiquitous language и command guard

Код в этой главе записан в прежней поверхности языка — со словами категория, объект, утилита. Сегодняшний компилятор её слова читает, но программой такой файл не считает: файл, где есть только утилиты, flang check отклоняет. Разбор задачи в главе верен; синтаксис переносится по таблице из главы «Старые модели».

Глава разбирает, где категория FTS совпадает с bounded context из DDD, а где заканчивается: FTS хорошо описывает факты и переходы состояния, но не заменяет агрегат, репозиторий и шину событий.

Минимальный пример

категория «Исполнение заказа»

  объект Заказ
    номер является строкой
    «готов к отгрузке» является состоянием «Готов к отгрузке»

  морфизм «Готовый заказ можно отгрузить»
    если «Готов к отгрузке»
    то «Отгрузить заказ разрешено»

Что делает компилятор

fts check разбирает категорию, объект и морфизм, проверяет, что состояние «Готов к отгрузке» типизировано, а условие морфизма ссылается на реально объявленный факт объекта. Если правило в морфизме ссылается на несуществующее состояние, компиляция не пройдёт: fts check вернёт диагностику раньше, чем код доедет до code review. fts prove (или fts certify/fts verify с context) идёт дальше: он проверяет, что для конкретного экземпляра заказа факт «готов к отгрузке» действительно равен да в данных, а не только объявлен в типе.

Категория как bounded context

Одна категория .fts — это один bounded context, а не вся предметная область компании. «Исполнение заказа» отвечает за переход от готовности к разрешению отгрузки. Оплата, бухгалтерия и складской учёт — другие контексты со своими моделями, даже если они читают то же поле заказы[номер] из общей базы.

Термины внутри категории обязаны совпадать с языком, на котором говорит бизнес. Если склад говорит «подтвердил приёмку», в модели должно быть «склад подтвердил», а не warehouseFlag или status === 3. Ubiquitous language в FTS не декларация в глоссарии — она исполняется: fts test падает, если пример не совпадает с правилом, записанным теми же словами, что использует предметный эксперт.

Морфизм как разрешённый переход состояния

Морфизм в FTS — это не произвольная функция, а конкретное разрешённое превращение факта в другой факт: «если готов к отгрузке, то отгрузка разрешена». Он не выполняет отгрузку и не двигает деньги — он утверждает допустимость перехода. Реальный переход (запись в БД, вызов курьерской службы) остаётся кодом снаружи. Это прямое соответствие DDD-принципу: правила предметной области отделены от инфраструктурных эффектов.

Command guard: почему допускает доказательство, а не проверка в сервисе

examples/spec/shipment-guard/guard.mjs показывает, как выглядит допуск команды на практике. Функция shipmentTheorem(orderNumber) строит текст теоремы, decideShipment компилирует его и вызывает prove:

export function decideShipment(orderNumber, context) {
  const source = shipmentTheorem(orderNumber)
  try {
    const proof = prove(compile(source), context)
    return { allowed: true, source, proof }
  } catch (error) {
    return { allowed: false, source, reason: error.message, diagnostics: error?.diagnostics ?? [] }
  }
}

Разница с обычным if (order.readyToShip) ship() в том, что здесь решение — не булево значение внутри сервиса, а результат независимого доказательства на переданном снимке данных. Запуск на реальной модели заказа:

node examples/spec/shipment-guard/guard.mjs ЗК-7781

даёт "allowed": true вместе с деривацией пути заказы[номер="ЗК-7781"].готов к отгрузке. Если склад не подтвердил приёмку, тот же номер заказа с другим context:

node examples/spec/shipment-guard/guard.mjs ЗК-7781 --blocked

возвращает "allowed": false и диагностику FTS_WITNESS_MISMATCH с точным путём и сравнением expected true, got false. Процесс завершается кодом 1, поэтому guard пригоден и для shell, и для CI, и для агентского пайплайна: ни объяснение агента, ни текст ответа не могут заменить эту проверку.

Отдельная деталь: номер заказа перед подстановкой в текст теоремы проверяется регулярным выражением /^[\p{L}\p{N}-]{1,32}$/u. Без этой проверки кавычки внутри значения могли бы сломать разбор и подменить формулировку теоремы — guard.test.mjs отдельно проверяет, что такой номер отклоняется до компиляции.

Сертификат или proof не заменяет optimistic lock. Между снимком данных и записью состояние может измениться, поэтому транзакционная граница и version check остаются обязанностью приложения, а не FTS.

Где проходит граница агрегата

FTS не хранит состояние и не управляет параллельным доступом. Модель описывает факты и правило перехода; application service собирает снимок агрегата, вызывает prove/verify и только при успехе выполняет команду. Размещайте .fts рядом с bounded context, а не в общей папке правил:

src/order-execution/
  domain/
  application/
  policies/
    shipment.fts
    shipment.context.schema.json

Версионируйте изменения политики как код: pull request с правкой .fts должен показывать изменённое правило, изменённые примеры и влияние на сгенерированные артефакты.

Практика в песочнице

Замените номер заказа в теореме и context на несуществующий и посмотрите на диагностику.

Типичные ошибки

  • FTS_NATURAL_DECLARATION — блок начинается не с объект, структура, морфизм, теорема или утилита». Опечатка вроде сущность Покупка` ловится компилятором до валидации типов.
  • FTS_WITNESS_MISMATCH — факт, на который ссылается теорема, не совпадает со значением в context (пример выше с --blocked). Это основной способ, которым command guard отказывает в допуске.
  • Плохой, но не диагностируемый компилятором антипаттерн: морфизм, который «доказывает» закон только потому, что он объявлен в .fts, без реального бизнес-обоснования у эксперта предметной области. Компилятор проверяет типовую согласованность, а не то, верен ли закон в реальности.

Упражнение

Возьмите команду с реальным бизнес-отказом из своего проекта — approve, refund, publish или deploy. Опишите минимальный факт допуска, один морфизм и конкретный snapshot, на котором prove должен и не должен проходить. Отдельно, текстом вне .fts, перечислите технические условия (лок, идемпотентность запроса, ретраи), которые остаются вне модели.

Чек-лист

  • Категория покрывает один bounded context, а не всю систему.
  • Имена полей и состояний совпадают с языком предметного эксперта дословно.
  • Морфизм описывает переход факта в факт, а не вызывает эффект.
  • Command guard использует prove/certify+verify на актуальном снимке, а не хранит «уже посчитанный» булев флаг.
  • Optimistic lock и транзакционная граница остаются в application service.
  • .fts лежит рядом с bounded context и ревьюится как код.

Кейсы каталога по этой теме

Дальше: AI-агенты и Digit

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов
Дальше