Материал FTS and DDD: bounded context, ubiquitous language and command guard
0%

FTS and DDD: bounded context, ubiquitous language and command guard

FTS and DDD: bounded context, ubiquitous language and command guard

The chapter explains where the FTS category coincides with the bounded context from DDD, and where it ends: FTS well describes facts and state transitions, but does not replace the aggregate, repository, and event bus.

Minimal example

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

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

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

What the compiler does

fts check parses category, object, and morphism, checks that the state «Ready for shipment» is typed, and the morphism condition refers to a fact actually declared for the object. If the rule in the morphism refers to a non-existent state, compilation will fail: fts check will return diagnostics before the code reaches code review. fts prove (or fts certify/fts verify with context) goes further: it checks that for a specific order instance, the fact «ready for shipment» is indeed equal to да in the data, and not just declared in the type.

Category as bounded context

One .fts category is one bounded context, not the entire company’s subject area. “Order execution” is responsible for transitioning from readiness to shipment resolution. Payment, accounting, and inventory accounting are other contexts with their own models, even if they read the same заказы[номер] field from the shared database.

Terms within a category must match the language used by the business. If the warehouse says «confirmed receipt», the model must have «склад подтвердил», not warehouseFlag or status === 3. Ubiquitous language in FTS is not a declaration in a glossary — it is executed: fts test fails if the example does not match the rule written in the same words used by the domain expert.

Morphism as an Allowed State Transition

A morphism in FTS is not an arbitrary function, but a specific allowed transformation of a fact into another fact: “if ready for shipping, then shipping is allowed”. It does not perform shipping and does not move money — it asserts the validity of the transition. The actual transition (a record in the DB, calling a courier service) remains as code outside. This is a direct correspondence to the DDD principle: domain rules are separated from infrastructure effects.

Command guard: why proof is allowed instead of verification in the service

examples/fts/shipment-guard/guard.mjs shows what access looks like in practice. The shipmentTheorem(orderNumber) function builds the theorem text, decideShipment compiles it and calls 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 ?? [] }
  }
}

The difference from the regular if (order.readyToShip) ship() is that here the decision is not a boolean value inside the service, but the result of an independent proof on the provided data snapshot. Running on a real order model:

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

yields "allowed": true together with the path derivation заказы[номер="ЗК-7781"].готов к отгрузке. If the warehouse did not confirm receipt, the same order number with a different

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

returns "allowed": false and diagnostics FTS_WITNESS_MISMATCH with the exact path and comparison expected true, got false. The process ends with code 1, so the guard is suitable for shell, for CI, and for an agent pipeline: neither agent explanation nor response text can replace this check.

A separate detail: the order number is checked by the regular expression /^[\p{L}\p{N}-]{1,32}$/u before being substituted into the theorem text. Without this check, quotation marks inside the value could break the parsing and alter the theorem’s formulation — guard.test.mjs separately checks that such a number is rejected before compilation.

A certificate or proof does not replace an optimistic lock. Between a data snapshot and a write, the state may change, so the transactional boundary and version check remain the responsibility of the application, not FTS.

Where the Aggregate Boundary Lies

FTS does not store state and does not manage parallel access. The model describes facts and transition rule; application service collects an aggregate snapshot, calls prove/verify, and only on success executes the command. Place .fts next to the bounded context, not in a shared rules folder:

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

Version control policy changes as code: a pull request with edit .fts must show the modified rule, modified examples, and the impact on generated artifacts.

Practice in the sandbox

Replace the order number in the theorem and context with a non-existent one and look at the diagnostics.

Common Fallacies

  • FTS_NATURAL_DECLARATION — block starts not with объект, структура, морфизм, теорема or утилита». Опечатка вроде Purchase entity` ловится компилятором до валидации типов.
  • 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 lies next to bounded context and is reviewed as code.

Cases in the catalog on this topic

Next: AI-agents and Digit

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

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

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

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