Расширение возможностей ограничений-проверок. Проблематика.

 

  

   При разработке бизнес-логики на сервере БД PostgreSQL, возникает потребность в повышении степени информированности пользователей при наступлении исключительных ситуаций в движке БД.

 

   Существует два наиболее распространенных, и при этом довольно полярных подхода к обработке исключительных ситуаций.

 

   Первый подход (наиболее популярный) заключается в том, что программа - клиент базы данных получает сообщение о наступлении исключительной ситуации, и как есть - выдаёт его пользователю, не пытаясь трансформировать в удобоваримый вид. Пример такого сообщения:

 

 

 

   

   Недостаток данного подхода очевиден, 100% пользователей не поймёт в чем суть ошибки. И будут либо привлекать службу поддержки, либо пытаться угадать в чем проблема.

 

   Второй подход (когда идет попытка заботы о качестве взаимодействия программы с пользователем) заключается в том, что в программе-клиенте в каждом месте где может произойти наступление исключительной ситуации, программист выписывает обработчики возможного спектра исключительных ситуаций для данного контекста, и опираясь на присланную с сервера информацию об исключении, пытается сформулировать её уже:

  • в терминах решаемой в данном месте прикладной задачи;

  • на нужном языке, понятном пользователю;

  • с выдачей дополнительных параметров, которые могут повлиять на понимание пользователем возникшей ситуации.

   Пример обработки предыдущего сообщения, с переводом на язык предметной области, с выдачей дополнительной информации, помогающей пользователю сразу понять в чем дело:

 

 

   

При этом подходе, также возникает ряд нюансов, существенно влияющих на конечный результат:

  • присланная сервером в рамках исключения информация может быть усечена (некоторые параметры, такие как общая длина сообщения, могут быть отрегулированы в настройках; другие параметры, такие как ограничение длины единичного поля значения, задаются в исходном коде БД, и не подлежат изменению без доработки и перекомпиляции движка БД);

  • информация о контексте (значениях столбцов, взаимосвязанных сущностях, ...) присылается в склеенном в общее строковое поле виде, и вычленить из неё интересующие значения либо невозможно, либо сопряжено с определенными трудностями (уникальный код парсинга, неустойчивость к последующим изменениям серверного кода, ...);

  • трудно предусмотреть в каждой прикладной точке полноценную реакцию на всё возможное многообразие возможных исключительных ситуаций. Зачастую программистом обрабатывается одно-два исключения, характерных в данной точке, а остальные события не обрабатываются и либо тихо проглатываются, либо идут в соответствии с первым подходом;

  • множество реакций на одинаковые ситуации приходится тиражировать в куче прикладных мест, а с учетом того что серверный код тоже подвержен модификациям и изменениям поведения, то синхронно с этим должны переосмысливаться/перерабатываться и все прикладные реакции, что как правило не осуществляется, и в итоге получается ворох мёртвого кода и/или кода, некорректно интерпретирующего возникающие исключительные ситуации, что приводит к тому что пользователь вводится в заблуждение.

 

Решение

   В данной статье рассматривается механизм обработки только одного класса исключительных ситуаций, возникающих при выполнении ограничений-проверок, выполняемых в отношении колонок таблиц. В данном виде исключения, контекстная информация пересылается в виде одной строки, и однозначно восстановить параметры ситуации из присланной строки не получается.

 

   Иллюстрация подхода демонстрируется на таблице с ограничением, которая классически описывается следующим образом:

 

CREATE TABLE temporary.no_go(

id integer,

note varchar,

description varchar

CHECK(id < 100)

)

Где ограничение проверяет значение поля id и выдаёт исключительную ситуацию, если оно превышает или равно значению 100.

 

Ограничение в предложенном подходе переписывается следующим образом:

 

CREATE TABLE temporary.no_go(

id integer,

note varchar,

description varchar

CHECK((id < 100) or prg.f_checkinfo_json(row_to_json(no_go.*)))

)

COMMENT ON CONSTRAINT no_go_check ON "temporary".no_go IS 'id = $id$ не проходит по условию (id < 100)'

 

 

Где:

 

CREATE OR REPLACE FUNCTION prg.f_checkinfo_json(t_row json)

RETURNS boolean

LANGUAGE plpgsql

STABLE

AS $function$

DECLARE

begin

RAISE NOTICE 'CHECKINFO JSON\n%', t_row;

return false;

end;

$function$

 

 

Тестовая ситуация воспроизводится следующим сценарием:

 

INSERT INTO temporary.no_go(id, note, description) VALUES(103, 'value', 'description');

 

   Чтобы увидеть полную картину, необходимо описать схему работы.

1. При проверке условия (срабатывающего при insert или update записи), если первая часть выражения CHECK (id < 100)выдала false, тогда (и только тогда) выполняется вторая часть prg.f_checkinfo_json(row_to_json(no_go.*)) которая выполняет следующие работы:

  • превращает ВСЕ ПОЛЯ текущей записи в json мэп, в которой ключом является имя колонки, а значением соответственно значение колонки в данной строке;

  • возбуждается событие RAISE NOTICE с передачей туда json-мэпа в виде строки, полученной на предыдущем шаге. При этом, RAISE NOTICE не прерывает выполнение текущей транзакции;

  • функцией prg.f_checkinfo_json возвращаетcя всегда false, чтобы сработал RAISE EXCEPTION;

  • возбуждается событие RAISE EXCEPTION из-за того что CHECK вернул false, после чего текущая транзакция прерывается.

2. Клиент базы данных (в ПО, разрабатываемом нашей компанией, применяется трехзвенка, поэтому клиентом БД является сервер приложений), получает последовательно два события: RAISE NOTICE (с приложенным json-мэпом со всеми необходимыми параметрами), RAISE EXCEPTION (сигнализирующем о наступлении ошибочной ситуации). Эти два события интегрируются в одно прикладное событие, дополняются комментарием данного ограничения, и (в нашей реализации), отправляются на клиент.

3. На клиенте, из полученного события извлекается комментарий ограничения. Если он задан, то происходит его парсинг на предмет извлечения из него прикладных полей. В данной иллюстрации 'id = $id$ не проходит по условию (id < 100)', из комментария извлекается тэг $id$, который раскрывается в значение 103 применяя присланный json-мэп, и затем сообщение собирается вновь в строку 'id = 103 не проходит по условию (id < 100)'. После этого, сообщение выводится пользователю.

 

Заключение

   Таким образом, за счет добавления в сервер приложений логики, обрабатывающий двойное событие, а также учитывающее особую семантику описания ограничений, получается решить ряд проблем комплексным образом:

  •  к месту обработки доставляется весь необходимый контекст ситуации;

  •  непосредственно рядом с ограничением в виде комментарии встраивается полезный текст обработчика;

  •  комментарий ограничения становится самоописываемой сущностью;

  •  полезный текст обработчика может опираться на весь спектр полей, который присутствует у данной записи;

  •  не требуется в каждой прикладной точке клиента описывать реакцию на наступление данной ситуации, что резко упрощает как разработку, так и последующее сопровождение программного кода.