Rules: domain logic
- Правила: свойства
- Правила: действия и присваивание
- Правила: события (WHEN)
- Правила: ограничения
- Правила: сессии изменений (NEWSESSION, APPLY)
Правила: свойства
Правила свойств
-
Ассистент НЕ ДОЛЖЕН объявлять свойство, если оно используется только один раз.
Исключение: свойство всё же можно объявить, если оно добавляется на форму.
-
Каждый параметр свойства ДОЛЖЕН использоваться в его выражении. Неиспользуемые параметры запрещены.
-
Ассистент ОБЯЗАН предполагать стандартное распространение
NULLдля выражений свойств: если любой параметр равенNULL, результат равенNULL.Исключения, которые НЕ обнуляют результат при одном операнде
NULL: операторы выбора —OVERRIDE, возвращающий первый не-NULLоперанд, иIF ... THEN ... ELSE, у которого устойчиво кNULLименно УСЛОВИЕ: условиеNULLуводит в веткуELSE, а не-NULLвозвращает значениеTHENкак есть, поэтомуIF TRUE THEN NULL ELSE 1— этоNULL, —MIN/MAX, устойчивая кNULLарифметика(+)/(-), конкатенацияCONCAT(операндNULLпропускается вместе со своим разделителем) и агрегатыGROUP(GROUP SUM,GROUP MAXи т. д.) — операнд или значениеNULLпропускается, а не распространяется.OR,NOTиXORтоже не распространяют: не-NULLоперанд читается какTRUE, поэтомуNULL OR TRUE— этоTRUE, аNOT NULL— тожеTRUE. Распространяет как разAND: он возвращаетTRUE, только когда оба операнда неNULL, поэтомуTRUE AND NULL— этоNULL.GROUP LASTпропускаетNULL, только пока у него нетWHERE: условием служит как раз не-NULL-ность агрегируемого выражения. При явномWHEREстрока, ему удовлетворяющая, отдает свое значение, даже если это значениеNULL.Эти исключения всё же дают
NULL, когда:- все операнды или агрегируемые значения равны
NULL— кромеNOT, весь смысл которого в том, чтобы ответить тамTRUE; (+)/(-)илиGROUP SUMдаёт0(нулевой результат возвращается какNULL).
- все операнды или агрегируемые значения равны
-
Ассистент НЕ ДОЛЖЕН использовать
GROUPс блокомBY(в том числеGROUP AGGR) внутри выражений: в приведении типа, в арифметике (включая(+)/(-)), как аргумент другого свойства или как реализацию абстрактного свойства через+=.Такой оператор сам задаёт параметры результата, поэтому допустим только как определение свойства целиком: правая часть определения через
=либо встроенное определение в квадратных скобках; в любой другой позиции платформа даёт ошибкуBY clause in GROUP operator cannot be used in expressions. Чтобы использовать результат в выражении, ассистенту СЛЕДУЕТ прежде всего переписать оператор безBY, заменив каждую группировку условием равенства с верхним параметром (GROUP SUM f(x) IF g(x) = y); иначе — применить встроенную форму[GROUP ... BY ...](...)к аргументам или объявить отдельное свойство и обращаться к нему.Ограничение связано именно с блоком
BY:GROUPбезBYберёт параметры из внешнего контекста и может использоваться внутри выражений.Рассуждая о
GROUP AGGR, ассистент ОБЯЗАН трактовать его какGROUP MAXс дополнительным ограничением. -
Ассистенту СЛЕДУЕТ избегать лишних условий, когда семантика языка уже даёт требуемый результат.
-
Ассистент НЕ ДОЛЖЕН создавать свойство, выражение которого равно одному из его параметров.
-
Ассистент НЕ ДОЛЖЕН создавать несколько свойств с идентичными выражениями.
-
Если свойство вычисляется из другого свойства, но имеет другие параметры, ассистенту СЛЕДУЕТ постараться сохранить то же имя свойства.
-
Чтобы проверить, что свойство равно
NULL, ассистенту СЛЕДУЕТ использоватьIF NOT property(...).Чтобы проверить, что оно не
NULL, ассистенту СЛЕДУЕТ использоватьIF property(...). -
Ассистенту СЛЕДУЕТ указывать
CHARWIDTHв определении свойства, а не в дизайне формы.
Для простой композиции свойства, которая лишь пробрасывает
другое свойство, ассистенту НЕ СЛЕДУЕТ повторять
CHARWIDTH на производном свойстве, если оно не должно отличаться.
-
Для статических объектов ассистент НЕ ДОЛЖЕН использовать свойства
staticCaptionилиstaticName.Вместо них ассистент ОБЯЗАН использовать
captionиname.Это касается и записи:
captionиname— простые композиции над хранимыми заголовком и именем, поэтому присваивание в них проходит в хранимое свойство. Заголовок статического объекта присваивается черезcaption; имя менять нельзя — изменениеnameзапрещено системным ограничением.nameвозвращает каноническое имя статического объекта —<пространство_имён>_<Класс>.<объект>, а не короткий идентификатор. Если нужна часть после точки, ассистенту СЛЕДУЕТ использоватьbasicNameиз системного модуляUtils. -
Имена свойств СЛЕДУЕТ делать краткими и избегать лишних слов.
-
Ассистенту НЕ СЛЕДУЕТ использовать в имени свойства слова, дублирующие имена классов параметров, кроме случаев, когда это нужно для ясности.
-
Ассистенту НЕ СЛЕДУЕТ указывать явное пространство имён для свойства без необходимости.
-
Создавая для собственного атрибута одного объекта DATA-свойство — или простую композицию от DATA-свойства (например, когда тянется имя связанного объекта), — ассистент ОБЯЗАН осознанно решить, относить ли его к системной группе
idилиbaseчерезIN.Атрибуты, образующие бизнес-идентификацию объекта и показываемые в его представлении, СЛЕДУЕТ относить к группе
id; прочие основные атрибуты — к группеbase(idвложена вbase).Свойство НЕ СЛЕДУЕТ помещать в
idилиbase, когда оно не является собственным основным атрибутом объекта. -
При делении значений целочисленных классов ассистент ОБЯЗАН приводить к
NUMERICодин из операндов, а не результат.Отношение двух целых чисел — целочисленное деление, поэтому внешнее приведение вида
NUMERIC[16,4](a * b / c)молча отбрасывает дробную часть; правильная форма —NUMERIC[16,4](a) * b / c. -
Класс в объявлении параметра (
prop(SubClass x)) — это сигнатура, а не фильтр времени выполнения: он разрешает одноимённые свойства и задаёт сигнатуру, но вычисляемое множество определяется свойствами, использованными в выражении. Чтение свойства родительского класса с параметром, объявленным классом-потомком, всё равно идёт по ВСЕМ объектам родительского класса (например, вGROUP SUM— молча неверные итоги).Чтобы ограничить множество классом, ассистент ОБЯЗАН добавить явное условие
x IS SubClass(или использовать свойство, объявленное на этом классе-потомке). -
В операторе
GROUP ... BYассистент НЕ ДОЛЖЕН перечислять в блокеBYверхние параметры, использованные в выражениях оператора: каждый такой параметр уже неявно является группировкой — параметром создаваемого свойства — и остаётся на своём месте в сигнатуре.При явном списке параметров слева
BY-выражения отображаются по порядку только на параметры, не использованные в выражениях; несовпадение количества или классов даёт ошибку. -
MAXиMIN— префиксные операторы над списком операндов через запятую (MAX a, b), а не инфиксные:a MAX bне разбирается — платформа выдаётno viable alternative at input 'MAX'.Список операндов тянется настолько, насколько позволяет выражение, поэтому всё после запятой принадлежит оператору:
MAX a, b * c— этоMAX(a, b * c), аx * MAX a, bкорректно и без скобок. Там, где следующий оператор должен применяться к самому максимуму, оператор ОБЯЗАН быть взят в скобки:(MAX a, b) * c.Эти операторы сравнивают операнды одной строки; максимум по строкам — это
GROUP MAX.
Правила абстрактных свойств (+=)
-
Класс значения реализации
+=ОБЯЗАН укладываться в класс значения, объявленный у абстрактного свойства; неявного приведения нет — реализация с более широким классом отвергается при старте сервера с ошибкой «wrong value class of implementation», в строкахspecifiedиexpectedкоторой указаны класс реализации и объявленный класс.Чаще всего класс расширяет арифметика, причём сильнее, чем кажется:
+и-— какMIN/MAXи операторы выбора — дают общего предка, расширяя целую часть и шкалу независимо, поэтому результат может оказаться шире любого из операндов:NUMERIC[16,2] + NUMERIC[10,4]— этоNUMERIC[18,4];*складывает и целые части, и шкалы:NUMERIC[16,2] * NUMERIC[10,4]— этоNUMERIC[26,6];/расширяет катастрофически: при настройках по умолчанию его шкала всегда равна максимальной шкалеNUMERIC(32), поэтомуNUMERIC[16,2] / NUMERIC[16,2]— этоNUMERIC[48,32].
Агрегат
GROUPв основном сохраняет класс того, что агрегирует, —GROUP SUM,GROUP MAXилиGROUP LASTпоNUMERIC[16,2]даютNUMERIC[16,2], — но выносит наружу то, до чего расширилось агрегируемое выражение. Сам по себе расширяетGROUP CONCAT: его результат — строка неограниченной длины (ISTRINGпри объявленномISTRING[250]). Обычная конкатенация строк тоже расширяет, складывая длины операндов (ISTRING[326]при объявленномISTRING[250]).Любое такое выражение ассистент ОБЯЗАН обернуть в явное приведение к объявленному классу:
f(X x) += NUMERIC[16,2](a(x) / b(x));f(X x) += ISTRING[250](a(x) + b(x));Для операндов целочисленных классов приведение ОБЯЗАНО сначала стоять на операнде, чтобы деление не оказалось целочисленным (см. правило 16 правил свойств); результат при этом всё равно расширяется до шкалы
32, как любое деление, поэтому внешнее приведение тоже нужно:f(X x) += NUMERIC[16,2](NUMERIC[16,2](a(x)) / b(x));
Правила упорядочивания (ORDER)
-
Там, где две строки могут разделить ключ порядка, а ответ зависит от того, какая из них победит, — какая из двух строк одной даты станет
GROUP LAST, какую из двух строк равного приоритета возьметTOP 1, на какую шагнет назадPARTITION PREV, — ассистент ОБЯЗАН выписать различитель явно, обычно сам объект:ORDER date(d), d.Для части таких случаев платформа дополняет неполный порядок сама, поэтому симптомом будет не разброс от запуска к запуску, а то, что выбрана та строка, которую отбирает служебный порядок по интерфейсам, — а его предметная область не просила. Выписанный различитель и делает выбор задуманным.
-
Накопительный
PARTITION SUM ... ORDERбезTOPиOFFSET— тот случай, когда различитель добавлять по привычке НЕ ДОЛЖНО. Его рамка по умолчанию дает всем строкам с одинаковым ключом порядка одно и то же накопленное значение. Добавленный различитель меняет результат — с итога на группу равных ключей на итог на строку, — а это решение о предметной области, а не мера предосторожности. ПодTOPилиOFFSETдействует обычное правило 1: они отбирают строки, и какие именно — стоит сказать. -
PARTITION LASTне читает порядок, чтобы вычислить свое значение: это значение текущей строки. Отбирает по порядкуGROUP LAST.
Правила: действия и присваивание
Правила действий
-
Ассистент ОБЯЗАН избегать
FOR, когда тот же результат можно выразить множественной (set-based) конструкцией.FORитерирует строку за строкой, и к нему СЛЕДУЕТ прибегать в последнюю очередь, когда нет декларативной альтернативы. Единственное измеренное исключение работает в обратную сторону: когда присваиваемое значение — агрегатGROUP, границы которого коррелируют с обновляемой строкой, и оба множества велики, множественный вариант может скомпилироваться в запрос, материализующий всю корреляцию, и построчныйFOR ... NOINLINEможет оказаться быстрее — там, где индекс по агрегируемому классу позволяет отвечать на агрегат каждой строки индексным обращением.Предпочитайте множественные альтернативы, например:
- агрегация или материализация множества
->
GROUP SUM,GROUP CONCAT,GROUP MAX,GROUP LAST,GROUP AGGR - присваивание свойства по множеству
-> прямое присваивание свойства с параметрами
вместо цикла
FOR ... DO - экспорт табличных или иерархических данных
->
EXPORT FROM,EXPORT JSON FROM,EXPORT XML FROM,EXPORT CSV FROM - построение структурированных данных
->
JSON FROM,XML FROM - массовые интеграционные записи
->
NEW,DELETEили множественное изменение свойства вместо построчногоFOR
FORприемлем, когда тело имеет настоящий построчный поток управления, такой как условныйAPPLY,MESSAGE,throwExceptionили внешние вызовы, которые нельзя выразить как операцию над множеством. - агрегация или материализация множества
->
-
Параметры, вводимые в
NEW alias = ClassиFOR expr(p) [NEW alias = Class] DO { ... }, НЕ следуют обычным правилам лексической области видимости из мейнстримных языков программирования.Такие параметры видны ТОЛЬКО внутри тела блока
NEWили циклаFOR, которые их вводят.Ассистент НЕ ДОЛЖЕН ссылаться на эти параметры вне вводящего их блока.
Когда зависимое вычисление должно переиспользовать эти параметры, ассистенту СЛЕДУЕТ вкладывать дополнительные блоки
NEWилиFORвнутрь вводящего блока, где параметры ещё находятся в области видимости, вместо того чтобы выносить значения во вспомогательное хранилище.И наоборот, параметр, объявленный внутри агрегата
GROUP, принадлежит этому агрегату и НЕ виден снаружи; в частности, он не может служить переменной цикла объемлющегоFOR. Объявите переменную собственным параметромFOR, а агрегат используйте только как логическое условие по ней. -
Ассистенту СЛЕДУЕТ избегать введения свойств
LOCALбез конкретной необходимости.LOCALматериализует временную таблицу в PostgreSQL, только когда содержит больше одной строки, поэтому стоимость выполнения, заметно превышающая стоимость стековой переменной в обычном языке, относится кLOCALс параметрами (буферы, ключуемые номером строки, значения по объектам).LOCALбез параметров содержит не больше одной строки и всегда хранится в памяти, поэтому флаги и одиночные значения без параметров дёшевы; избегать их стоит не из-за стоимости, а чтобы не плодить сущности. -
LOCALобычно оправдан, когда выполняются ОБА условия:- его значение нетривиально вычислить (агрегация, join'ы, многошаговая логика, внешние вызовы или другая работа, которую стоит материализовать), И
- то же значение используется более одного раза, так что материализация позволяет избежать повторного вычисления.
-
По возможности ассистенту СЛЕДУЕТ предпочитать альтернативы новому
LOCAL:- встраивать выражение в каждое место использования, если оно дешёвое
- вкладывать блоки
NEW/FOR, чтобы промежуточные значения оставались в области видимости параметров - использовать обычное (не
LOCAL) вычисляемое свойство, когда значение переиспользуется в нескольких действиях
-
Это рекомендации, а не жёсткие запреты. Если ассистент не может найти рабочий синтаксис для конструкции без
LOCALили другой подход постоянно не получается и не выходит построить чистое действие, откат кLOCALприемлем как последнее средство.Устоявшиеся паттерны
LOCAL, предписанные другими правилами (например, промежуточное хранение при импорте, перенос между вложенными сессиями), остаются допустимыми; ассистенту СЛЕДУЕТ держать такиеLOCALминимальными по количеству и области. -
Параметры верхнеуровневых операторов тела действия разделяют один контекст параметров: одинаковые имена обозначают один и тот же параметр, а класс параметра объявляется только при первом использовании.
В генерируемых скриптах (
eval, наполнение данных) ассистенту СЛЕДУЕТ давать параметрам верхнеуровневых операторов уникальные имена, чтобы не зависеть от порядка операторов. -
Многие системные служебные действия возвращают результат через одноимённое локальное свойство без параметров (например, в
Utils: действиеfileExists[ISTRING[500]]пишет в свойствоfileExists[]). Такой элемент — ДЕЙСТВИЕ, а не логическое свойство: ассистент ОБЯЗАН сначала вызвать действие, а затем прочитать свойство без параметров (fileExists(path); IF fileExists() THEN ...), и НЕ ДОЛЖЕН использовать форму с параметрами внутри выражения (IF fileExists(path)— неверно).
Правила присваивания (<-)
-
Аргументы изменяемого свойства в левой части
<-могут быть выражениями от параметров оператора (sentFolder(account(f)) <- f), но новые локальные параметры вводятся только как типизированные параметры, а не внутри выражений. Запись «по вычисляемому ключу» по аналогии с императивнымmap[key] = valueлегко нарушает это.Поэтому, перенаправляя ссылки-самоссылки при глубоком копировании графа объектов, ассистенту СЛЕДУЕТ держать обратное отображение и итерироваться с ЦЕЛЕВЫМ объектом в роли параметра —
link(Copy n) <- newOf(link(srcOf(n))) WHERE spec(n);— а не писатьlink(newOf(x)) <- newOf(link(x)); -
<- выражение IF условиеприсваивает всё выражение ВСЕМ объектам: там, где условие не выполняется, свойство перезаписываетсяNULL. Фактически это сброс-плюс-запись.ДОБАВЛЯЯ присваивание к свойству, уже заполненному ранее в том же действии, ассистент ОБЯЗАН использовать форму
WHERE(prop(x) <- TRUE WHERE cond(x)), которая меняет только строки, подходящие под условие. Второе присваивание в форме IF тому же свойству ОБЯЗАНО рассматриваться как сигнал тревоги при ревью. -
Написанный прямо в теле действия или события
PREV(<выражение>)переносит в состояние начала сессии ВСЁ обёрнутое выражение, включая подвыражения-аргументы: аргумент, вычисленный в текущей сессии (LOCAL, свойство объекта, созданного в сессии), внутриPREVчитается какNULL, молча обнуляя результат.Чтобы прочитать предыдущие данные при текущих аргументах, ассистент ОБЯЗАН обернуть
PREVв отдельное свойство —prevF(x) = PREV(f(x));— и вызывать его, а не писатьPREV(f(<вычисленный в сессии аргумент>))в теле. -
Параметр, через который читается или изменяется свойство с именем, объявленным у нескольких классов, ОБЯЗАН быть аннотирован классом при первом использовании (
date(Interaction i) <- ...): перегруженное имя разрешается по классам параметров, и нетипизированный параметр даёт ошибку «ambiguous name». Особенно это касается событий: их оператор — отдельный контекст параметров, в котором класс больше ниоткуда не выводится, а условиеi IS Interactionкласс параметра не задаёт.
Правила циклов (FOR, WHILE)
-
FORфиксирует свой набор до первой итерации: условие вычисляется один раз, подходящие строки читаются, и тело выполняется по разу на строку этого набора. То, что меняет тело, — включая данные под условием — не добавляет и не убирает итераций.Перечитывает набор
WHILE, но делает это по ШАГАМ, а не по строкам: один шаг заново вычисляет условие, читает весь подходящий набор и выполняет тело для каждой его строки, и только потом набор читается снова; итерации прекращаются, когда он возвращается пустым. Поэтому строка, уже попавшая в текущий шаг, свою очередь получит, даже если более ранняя строка того же шага сделала условие для нее ложным. -
Без
ORDERFORобходит свой набор в произвольном порядке. Ассистент ОБЯЗАН задавать явныйORDERвсюду, где результат зависит от последовательности — нумерация, накопительные итоги, любое чтение записанного предыдущей итерацией, — и всюду, гдеTOPограничивает число взятых строк, и ОБЯЗАН завершать этотORDERключом, различающим любые две строки.
Правила потоков (NEWTHREAD, NEWEXECUTOR)
-
Серверное поточное действие разделяет сессию изменений вызывающего кода, а сессии изменений не потокобезопасны.
Поэтому ассистенту СЛЕДУЕТ оборачивать тело серверного
NEWTHREADвNEWSESSION, а когда ему нужна собственная транзакция базы — вNEWSESSION NEWSQL. Это размен: обычныйNEWSESSIONперестает видеть несохраненные изменения вызывающего, поэтому обертка опускается только там, где разделение сессии сделано намеренно И известно, что одновременно они не выполняются.Что дает обертка внутри транзакции
APPLY, зависит от того, КОГДА тело стартует на самом деле. Пока транзакция открыта,NEWSESSION, включаяNEWSQL, сессию не создает — действие откладывается в текущую, — а проверка происходит в момент выполнения тела, а не в момент его планирования.SCHEDULE DELAY— это число миллисекунд, а не барьер, ждущий применения, так что и он ничего не гарантирует. Ассистент НЕ ДОЛЖЕН рассчитывать, что поток, запущенный из глобального обработчика, окажется изолированным.Клиентский executor — обратный случай: действие доставляется в соединение пользователя и выполняется там в собственной свежей сессии, так что оборачивать его незачем.
Правила: события (WHEN)
Правила событий (WHEN)
-
Событие
WHENсрабатывает каждый раз, когда его условие становится истинным в текущей сессии, и записывает целевое свойство безусловно. Если то же целевое свойство также изменяется явно где-то ещё в сессии (ввод пользователя, присваивание в действии, импорт), событие перезаписывает это явное изменение. -
Когда задача события — лишь вывести или подставить значение по умолчанию из других входов, ассистенту СЛЕДУЕТ защищать условие через
AND NOT CHANGED(<целевое>)для каждого целевого свойства, в которое пишет событие.Это предотвращает затирание событием явного изменения целевого свойства, сделанного в той же сессии.
-
Защиту СЛЕДУЕТ опускать только когда событие должно принудительно перебивать любое явное изменение — например, поддерживаемые итоги, аудит-метки или инварианты, обходить которые пользователю не позволено.
-
Правила 1-3 описывают форму события-действия
WHEN <условие> DO <целевое> <- <выражение>. Форма вычисляемого события<целевое> <- <выражение> WHEN <условие>ведет себя иначе: его изменение вычисляется при обращении к целевому свойству, и явное изменение этого свойства в сессии приоритетнее изменения события.Поэтому для подстановки значения по умолчанию, уступающей явному изменению, достаточно самой формы вычисляемого события — защита не нужна. Проверять
CHANGED(<целевое>)в его условии в любом случае нельзя: целевое свойство стало бы зависеть от собственного изменения, образуя цикл<целевое>->CHANGED(<целевое>)-><целевое>.При отсутствии явного изменения событие записывает значение выражения и тогда, когда оно
NULL. -
Условие
WHENпроверяется и на удаленных объектах. Удаление объекта сбрасывает его первичные свойства вNULL, поэтому условие, реагирующее на превращение значения вNULL, выполняется для каждого удаленного объекта, у которого значение было неNULL, и обработчик отрабатывает на уже несуществующем объекте.Какие это операторы изменения, решает переход, который каждый из них покрывает:
DROPPED,CHANGED,DROPCHANGEDиSETDROPPEDвключают переход из не-NULLвNULLи потому срабатывают на удалении;SETиSETCHANGEDтребуют, чтобы новое значение было неNULL, и не срабатывают.Там, где условие может сработать на пути в
NULL, а обработчик не должен действовать на удалении или уходе объекта из класса, оно ОБЯЗАНО быть сужено через<объект> IS <Класс>.
Когда на самом деле выполняются локальные события
-
Локальный обработчик события выполняется не в момент изменения данных, а в одной из точек жизни сессии: синхронизация формы, открытие формы, начало
APPLY, создание вложенной сессии либо явный вызовSystem.executeLocalEvents[].Вне интерактивной формы — в действии, вызванном из внешней системы, в задании планировщика — из этих точек обычно случается только применение. Поэтому чтение свойства сразу после изменения данных, от которых оно зависит, вернет значение БЕЗ применения локальных обработчиков — в отличие от вычисляемого свойства, которое всегда актуально.
Ассистент НЕ ДОЛЖЕН рассчитывать в таком месте на то, что локальный обработчик отработал: либо это делает
APPLY, либо перед чтением вызываетсяSystem.executeLocalEvents[].
Правила: ограничения
-
Когда выбор значения в одном свойстве должен быть ограничен значениями других свойств — соседние поля той же формы, текущий контекст, связанные объекты — ассистенту СЛЕДУЕТ в первую очередь рассматривать
CONSTRAINT ... CHECKED BY <свойство>.CHECKED BYзаставляет диалог изменения для указанного свойства автоматически фильтровать варианты, нарушающие ограничение, так что запрет применяется декларативно в момент выбора, а не постфактум.Фильтр доходит только до этих диалогов изменения. Механизм ввода, предлагающий значения как-то иначе, его не использует, и там нарушающее значение отвергается только при проверке самого ограничения.
-
Откатываться к ручным фильтрам на форме или валидирующим действиям СЛЕДУЕТ, когда
CHECKED BYне способен выразить ограничение (например, фильтр зависит от состояния UI, не смоделированного как свойство, или правило носит рекомендательный, а не обязательный характер), — а также когда ограничение ВЫРАЗИМО так, но значение выбирается другим механизмом, до которого фильтрCHECKED BYне доходит. -
Ассистенту НЕ СЛЕДУЕТ помещать в условие
CONSTRAINTтяжелые группировки по большим таблицам (особенно вложенные нематериализованные): инкрементальная проверка при применении может развернуться в непрактично большой запрос, даже при заданных для свойств хинтах вычисления.Для таких дорогих проверок используйте событие
WHEN: дешевое условие-детектор изменений, чтение тяжелых значений вLOCALв обработчике, затемMESSAGE+CANCELпри нарушении.
Правила: сессии изменений (NEWSESSION, APPLY)
-
Прежде чем вводить
NEWSESSION, ассистент ОБЯЗАН решить, какое поведение сессии требуется. Ни один из вариантов ниже не работает внутри транзакцииAPPLY— в обработчике глобального события или в применяемом действии, — где сессия не создается вовсе: внутреннее действие откладывается и выполняется в текущей сессии, в той же транзакции. Ассистент НЕ ДОЛЖЕН рассчитывать там на независимую фиксацию.- изолированная независимая единица ->
NEWSESSION - изолированная единица, которая также должна видеть отдельные локальные
свойства верхней сессии ->
NEWSESSION NESTED (...) - изолированная единица, которая должна видеть все локальные свойства
верхней сессии ->
NEWSESSION NESTED LOCAL - дочерний диалог или редактор, который должен работать с несохранёнными
объектами верхней сессии и возвращать свои изменения в эту верхнюю сессию
->
NESTEDSESSION
- изолированная независимая единица ->
-
Для действий, добавленных на формы, есть два основных паттерна:
- паттерн readonly-формы: форма фактически только для просмотра, поэтому добавленные на неё действия СЛЕДУЕТ по умолчанию выполнять в новой сессии
- паттерн редактируемой формы:
форма имеет редактируемые свойства, поэтому любое добавленное на неё действие,
использующее
NEWSESSION, ДОЛЖНО либо:APPLY;IF canceled() THEN RETURN;передNEWSESSION, либо быть полностью независимым от несохранённых изменений в этой форме
-
Простой
NEWSESSION— вариант по умолчанию для изолированной работы, которая не должна случайно применить ожидающие изменения формы вызывающей стороны.Типичные паттерны в исходниках:
- readonly-списочные формы с
PROPERTIES(...) NEWSESSION NEW, EDIT, DELETE - смена статусов или создание зависимых документов
после предшествующего
APPLY - внешние или интеграционные действия, изолирующие HTTP-вызовы и сохраняющие собственные результаты
- небольшие немедленные обновления UI с
NEWSESSION { APPLY { ... } }
- readonly-списочные формы с
-
Если внутренняя логика зависит от локального состояния верхней сессии, такого как выделения, отметки или буферы импорта, ассистент ОБЯЗАН явно перенести это состояние через
NESTED (...)илиNESTED LOCALв операторе либо объявить само свойствоDATA LOCAL NESTED— такое переносится без перечисления в операторе. ПодNEWSQLне работает ни то, ни другое: на собственном соединении не переносится ничего, поэтому ассистент НЕ ДОЛЖЕН сочетатьNEWSQLс зависимостью от локального состояния верхней сессии. -
Успешный
APPLYочищает сессию, а с ней по умолчанию и всеLOCAL-свойства в ней: после возврата из такогоAPPLYобычныйLOCALснова пуст.APPLY, который не прошел или был отменен, оставляет сессию как была, вместе с локальными, — поэтому ассистент НЕ ДОЛЖЕН читатьLOCALпослеAPPLY, чтобы отличить успех от неудачи; их различаетcanceled(). Внутри вложенной сессии очистки нет вовсе: изменения копируются в родительскую сессию, а вложенная остается стоять вместе со своими локальными.Вне вложенной сессии значение
LOCALпереживает УСПЕШНЫЙAPPLY, когда выполнено ХОТЯ БЫ ОДНО из:LOCALобъявлен какNESTEDв момент объявления (LOCAL NESTED name = Type ();илиname = DATA LOCAL NESTED Type (...);), ИЛИAPPLYявно сохраняет его черезAPPLY NESTED (name1, ..., nameN)илиAPPLY NESTED LOCALдля всех локалов.
Ассистент НЕ ДОЛЖЕН рассчитывать, что значение обычного
LOCAL, вычисленное до УСПЕШНОГОAPPLY, останется доступным после него. Сохраняют его два случая: вложенная сессия, где не очищается ничего, и применение, которое не прошло или было отменено, — оно оставляет сессию как была. Если накопленное значение должно жить дольшеAPPLY— например, буфер импорта, читаемый в пост-apply доработке — ассистент ОБЯЗАН либо объявить его сNESTED, либо перечислить его вAPPLY NESTED (...)(или использоватьAPPLY NESTED LOCAL) в месте вызова. -
При использовании
NEWSESSION NESTED (...)илиNEWSESSION NESTED LOCALассистенту СЛЕДУЕТ сохранять те же вложенные локальные свойства наAPPLY, если результат должен быть скопирован обратно в верхнюю сессию, например черезAPPLY NESTED (...)илиAPPLY NESTED LOCAL. -
Ассистент НЕ ДОЛЖЕН заменять
NESTEDSESSIONпростымNEWSESSIONдля дочерних форм или диалогов, привязанных к родительскому объекту, который может быть ещё не сохранён в текущей сессии формы. -
Прежде чем открывать свежую
NEWSESSIONиз действия, запущенного на форме редактирования, ассистенту СЛЕДУЕТ решить, нужно ли сначала сохранить текущие изменения формы.Распространённый паттерн:
APPLY;IF canceled() THEN RETURN;NEWSESSION { ... }Этот паттерн используется перед сменой статусов, генерацией документов и другими изолированными последующими действиями.
-
После
APPLYассистент ОБЯЗАН проверятьcanceled()только тогда, когда последующая логика зависит от того, удалось ли сохранение — для раннего выхода, пропуска побочного эффекта или отката отложенной работы.APPLYв интерактивном контексте сам показывает пользователю сообщение об ограничении. Ассистент НЕ ДОЛЖЕН добавлятьIF canceled() THEN MESSAGE applyMessage()послеAPPLYв интерактивных действиях только для того, чтобы сообщить об ошибке — это дублирует сообщение, которое уже показала платформа. Явный вывод черезapplyMessage()илиthrowException(applyMessage())нужен только для не интерактивных вызывающих сторон (API-эндпоинты, фоновые интеграции), где никакой диалог не показывается.Если
APPLYне проходит из-за ограничения, изменения остаются несохранёнными в текущей сессии, и любой последующийAPPLYв той же сессии также не пройдёт, пока проблемные данные не исправлены или изменения не отменены (например, черезCANCEL). -
Ассистенту СЛЕДУЕТ держать блоки
NEWSESSIONнебольшими и целевыми: изолировать одну единицу работы, применить её при необходимости и выйти.Ассистент НЕ ДОЛЖЕН вводить
NEWSESSIONлишь для сокрытия багов видимости сессии. Если изменения верхней сессии должны оставаться видимыми, требуется семантика вложенной сессии. -
Тело
APPLYможет выполниться не один раз. Транзакция применения МОЖЕТ быть автоматически повторена после update conflict, deadlock или таймаута — будет ли, зависит от сбоя и от лимита попыток, — а применяемое действие и синхронные глобальные обработчики находятся внутри того, что повтор выполняет заново.Поэтому они ОБЯЗАНЫ выдерживать повтор. Необратимый внешний побочный эффект — отправка письма, вызов HTTP API, печать, запись файла — НЕ ДОЛЖЕН выполняться там: его место после успешного применения, где
canceled()говорит, состоялось ли оно.