FTS и DDD: bounded context, ubiquitous language и command guard
Категория FTS хорошо совпадает с bounded context, но не заменяет всю доменную модель.
категория «Исполнение заказа»
объект Заказ
номер является строкой
«готов к отгрузке» является состоянием «Готов к отгрузке»
морфизм «Готовый заказ можно отгрузить»
если «Готов к отгрузке»
то «Отгрузить заказ разрешено»
Термины должны совпадать с языком команды. Если бизнес говорит «скомплектован»,
не вводите техническое ReadyForShipment, только потому что так удобнее
назвать boolean.
Command guard
Application service собирает snapshot, проверяет его и только затем вызывает aggregate или внешний порт:
async function shipOrder(orderId: string) {
const snapshot = await orders.snapshot(orderId)
const certificate = certify(shipmentPolicy, { заказы: [snapshot] })
const decision = verify(shipmentPolicy, { заказы: [snapshot] }, certificate)
if (!decision.valid || decision.status !== "verified") {
throw new Error("Отгрузка не разрешена")
}
await shipping.createShipment(orderId)
}
Сертификат не заменяет optimistic lock. Между snapshot и записью состояние может измениться, поэтому транзакционная граница и version check остаются обязанностью приложения.
Где хранить FTS
Размещайте модель рядом с bounded context, например:
src/order-execution/
domain/
application/
policies/
shipment.fts
shipment.context.schema.json
Версионируйте изменения политики как код. Pull request должен показывать правило, примеры и влияние на generated artifacts.
Антипаттерны
- Одна категория «Вся компания».
- Поля
flag1,statusCode42вместо предметных терминов. - Морфизм, который якобы доказывает закон только потому, что он объявлен.
- FTS-модель, которая пытается управлять repository и event bus.
- Command guard без повторной проверки версии aggregate.
Упражнение
Выберите команду с реальным бизнес-отказом: approve, refund, publish или
deploy. Опишите минимальные факты допуска, один морфизм и конкретный snapshot.
Отдельно запишите технические условия, которые остаются вне FTS.