Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Homepage Карта сайта Версия для печати

Джентльменский набор Web-разработчика   Ларри Уолл о Perl6   Наблы Система Orphus
 

46. Теория: модерируемые справочники в БД

[25 января 2008 г.] обсудить статью в форуме

В данной статье я попытался описать опыт последних нескольких лет работы над сложной и высоконагруженной системой, к которой предъявляются повышенные требования к "чистоте" и качеству данных. При этом сами данные пополняются не столько администраторами системы, сколько ее пользователями, что входит в серьезное противоречие с требованием "чистоты". Ведь известно, что основная масса пользователей склонна вводить всякий "мусор" в поля, где предполагается наличие качественных данных.

Лирическое отступление 
Статья, вероятно, получилась нескольпо абстрактной и наукообразной. Тем не менее, я уверен, что, взяв за основу изложенные в ней идеи, каждый сможет реализовать в своей системе качественную и универсальную работу с "чистыми" справочниками, пополняемыми "некачественными" пользователями.

Структура справочников

Справочник - это абстрактный список, хранящий в себе некоторые "константные" значения (например, города, станции метро и т. д.). На элементы этого списка могут ссылаться различные компоненты программы или данные в БД. Справочник может содержать как "чистые" элементы (одобренные администратором), так и "грязные" (добавленные пользователями системы и могущие содержать некорректные данные).

 

В дальнейшем будем использовать следующую реляционную терминологию:

 

Таблица (table). Контейнер для списка, составляющего справочник.

Строка (row). Один из элементов справочника.

Поле (field). Составная часть элемента справочника. Строки состоят из имен полей и ассоциированных с ними значений.

Первичный ключ (PK). Уникальный идентификатор элемента справочника.

Флаг грязноты (dirty flag). Поле элемента справочника, в котором хранится признак, одобрен ли элемент модератором.

Детерминант (determinant). Набор полей, полностью определяющий ценную часть информации в элементе списка. Иными словами, совокупность полей, составляющая детерминант, и есть те данные, которые хранит список. Рассмотрим, например, список улиц в определенном городе. Каждый элемент этого списка содержит следующую существенную часть: имя улицы (street_name) и ссылка на город, которому принадлежит данная улица (city_id). Совокупность этих полей и составляет детерминант справочника.

 

Типичная структура справочника (в реляционной терминологии):

 

CREATE TABLE list(

  id INTEGER,

  det1_field ANYTYPE,

  det2_field ANYTYPE,

  det3_field ANYTYPE,

  ...,

  is_dirty TINYINT,

  UNIQUE INDEX (det1_field, det2_field, det3_field, ...)

);

 

id

Первичный ключ таблицы.

 

det1_field, det2_field, det3_field

Поля, входящие в детерминант таблицы.

 

is_dirty

Если false, элемент справочника "чистый", т.е. гарантировано, что он существует в действительности и одобрен администратором. Например, в справочнике street улица "Профсоюзная", которая там существует изначально и добавлена администратором, - "чистая". Улица же "Abcd", добавленная пользователем вручную, которую не одобрил или не успел одобрить администратор, - "не чистая".

 

Операции над справочником в приложении (IApplicationPeer)

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

 

array getDeterminantFields()

Возвращает массив имен полей, составляющих детерминант справочника.

 

string getPkField()

Возвращает имя PK-поля таблицы.

 

string getDirtyFlagField()

Возвращает имя поля признака "грязноты".

 

IReadonlyRow retrieveByDeterminant(array fields)

Возвращает readonly объект-строку справочника, описанную значениями полей детерминанта. Если строка не найдена, создает новый элемент справочника с указанным детерминантом, помечает его "грязным" и возвращает в приложение. В случае, если в fields заданы не все поля, составляющие детерминант, возбуждает исключение. Нужно заметить, что объект, возвращаемый этой функцией, доступен в режиме "только чтение". Он не может быть изменен. Объект может быть как "грязным", так и "чистым".

 

IReadonlyRow retrieveByPk(string pk)

Возвращает readonly объект-строку справочника по ее первичному ключу. Если строка не найдена, возвращает null. Полученный объект доступен только для чтения. Объект может быть как "грязным", так и "чистым".

 

readonly array retrieveListByFields(array fields)

Возвращает массив "чистых" элементов по запросу, определенному в массиве fields. Каждый элемент массива содержит в ключе имя поля, а в значении - его допустимую величину (либо массив величин, любая из которых допустима). Поиск осуществляется путем AND-объединения условий совпадения со всеми указанными величинами. Заметим, что в любые списки попадают только "чистые" строки справочника. В приложении невозможно получить список, содержащий наряду с "чистыми" еще и "грязные" элементы.

 

Следует обратить внимание на то, что интерфейс доступа к справочнику намеренно узок и не позволяет изменять элементы справочника напрямую. Он даже не позволяет определить, существовал ли уже элемент, полученный по retrieveByDeterminant(), или же был добавлен только что.

 

Операции над справочником в административном интерфейсе (IAdminPeer)

В административном интерфейсе доступны любые операции над справочником. Возможно прямое редактирование элементов, пометка элементов "чистыми" и т. д. Также гарантировано, что реализуется интерфейс IAdminPeer, содержащий следующие операции:

 

void mergeTo(string oldPk, string newPk);

"Сливает" воедино записи справочника с первичными ключами oldPk и newPk. После слияния элемент oldPk удаляется, а все ссылки на него заменяются ссылками на newPk.

 

Интерфейсы IReadonlyRow и IRow

Интерфейс IReadonlyRow определяет операции, допустимые с read-only строкой справочника. Необходимо также следить, чтобы все классы, реализующие IReadonlyRow, не допускали своего изменения. Интерфейс содержит как минимум следующие методы:

 

string getPk()

Возвращает первичный ключ строки.

 

string getDirty()

Возвращает признак "грязноты" данной строки справочника. Если значение равно false, элемент "чистый".

 

Интерфейс IRow является наследником IReadonlyRow и определяет как минимум одну дополнительную операцию:

 

void setDirty(mixed dirty)

Устанавливает признак "грязноты" записи. Если dirty = false, то запись "чистая", в противном случае - она "грязная".

Кэширование справочников

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

 

Сортировка справочников

Операция IApplicationPeer::retrieveListByFields() возвращает элементы справочника, отсортированные тем или иным способом. Однако порядок сортировки никак не специфицируется интерфейсом IApplicationPeer и полностью задается приложением. Как правило, порядок сортировки в справочниках определяется специальным полем sort, не входящим в детерминант справочника. Однако возможны ситуации, когда необходимы другие, более сложные критерии сортировки (например, сортировка по имени улицы). Операция retrieveListByFields() должна обрабатывать все такие ситуации внутри себя. 

 

О вреде миграции данных в справочниках между DEV и PROD серверами

Существуют две противоположные схемы модерации справочников.

 

  1. Справочники редактируются администратором на продакшен-базе данных (PROD). Редактирование заключается в исправлении очевидных опечаток, а также в "одобрении" элементов справочника (пометке их "чистыми"). При этом весьма желательно двухуровневая схема одобрения: данные, полученные от пользователя, помечаются "очень грязными" (is_dirty = 2). Их редактирует младший администратор и помечает уже "просто грязными" (is_dirty = 1). В дальнейшем все "просто грязные" данные вторично просматриваются старшим администратором, который может либо "одобрить" запись (is_dirty = 0), либо же отклонить изменение и вернуть элемент обратно младшему администратору (is_dirty = 2).
  2. Сведения о "грязных" записях закачивается с продакшен-сервера (PROD) на сервер разработки (DEV). В дальнейшем работа по "чистке" данных происходит на DEV-сервере, а под конец - новые "чистые" записи переносятся на PROD-сервер методом инкрементного копирования.

 

Нужно заметить, что метод (2) на практике оказывается в несколько раз сложнее, чем метод (1). Вот причины этого:

 

  • Необходимо организовать поток данных из продакшен-зоны в дев-зону, что чревато проблемами с безопасностью.
  • Возникают проблемы в дев-зоне. Ее уже нельзя случайно "ломать", т.к. там постоянно идет модерация. Нельзя также "затирать" базу данных, т.к. можно потерять ценные отмодерированные записи.
  • Сильно усложняется процесс перенося промодерированных записей с дев-зоны в продакшен-зону. Приходится организовывать процедуру, при которой "чистые" данные копируются в систему контроля версий, а затем, при проведении релиза, "накатываются" на продакшен-сервер.
  • Процедуру "наката" приходится делать "инкрементной": копировать только те данные, которые реально изменились в справочнике, и не трогать остальные. Осуществлять полную "чистку + копирование" справочника нельзя: во-первых, нарушается целостность по внешним ключам, а во-вторых, не срабатывают триггеры, которые, возможно, имеются на элементах справочника.
  • Наконец, как следствие предыдущего, процесс "накатывания" изменений оказывается существенно недетерминированным. Дело в том, что при накатывании в БД могут возникнуть конфликты уникальности записей, которые не удается решить автоматически. Например, администратор может переименовать запись A в запись B, а запись B - в запись A. При этом невозможно осуществить автоматическое инкрементное "накатывание" из-за конфликта уникальности записей.

 

Таким образом, вариант (1) модерации прямо на продакшен-сервере представляется значительно более выигрышным. К его недостаткам можно отнести разве что некоторое увеличение опасности при редактировании данных (можно случайно испортить много элементов справочника), однако эта проблема решается введением двух ролей - "младшего" и "старшего" администраторов. Вариант (1) также исключает необходимость опасной процедуры миграции данных между различными зонами, что улучшает безопасность системы в целом и делает ее проще.

 

 

обсудить статью в форуме

 
Рекламный блок
   

На странице:
    46. Теория: модерируемые справочники в БД
Структура справочников
     Операции над справочником в приложении (IApplicationPeer)
     Операции над справочником в административном интерфейсе (IAdminPeer)
     Интерфейсы IReadonlyRow и IRow
     Кэширование справочников
     Сортировка справочников
     О вреде миграции данных в справочниках между DEV и PROD серверами

Важное объявление:
    автор категорически против копирования и распространения в Интернете всех статей «Куроводства» с возрастом, меньшим 6 месяцев. Печальный опыт «расползания» чрезвычайно устаревших ошибочных версий статьи про Apache действительно объясняет такое решение.

Орфография на «Куроводстве»:
    если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Выделенный текст будет немедленно отослан вебмастеру, а Вы даже ничего и не заметите — настолько быстро все произойдет.

На заметку:
    если вы уже вскипели насчет дизайна этой страницы, то присмотритесь повнимательнее к названию, почитайте FAQ, сходите по лебедевским местам, как это уже предлагалось выше. Можно ли считать пародию плагиатом? Надеюсь, что нет.

Параметры этой страницы
   
GZip

Ссылки от спонсоров
    software registration service


Дмитрий Котеров | 25 января 2008 г. ©1999-2017 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению