Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). 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

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


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