![]() |
![]() |
![]() |
![]() |
![]() |
![]() ![]() ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
[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, содержащий следующие операции для работы со своими данными из приложения.
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, содержащий следующие операции:
void mergeTo(string oldPk, string newPk);
"Сливает" воедино записи справочника с первичными ключами oldPk и newPk. После слияния элемент oldPk удаляется, а все ссылки на него заменяются ссылками на newPk.
Интерфейс IReadonlyRow определяет операции, допустимые с read-only строкой справочника. Необходимо также следить, чтобы все классы, реализующие IReadonlyRow, не допускали своего изменения. Интерфейс содержит как минимум следующие методы:
string getPk()
Возвращает первичный ключ строки.
string getDirty()
Возвращает признак "грязноты" данной строки справочника. Если значение равно false, элемент "чистый".
Интерфейс IRow является наследником IReadonlyRow и определяет как минимум одну дополнительную операцию:
void setDirty(mixed dirty)
Устанавливает признак "грязноты" записи. Если dirty = false, то запись "чистая", в противном случае - она "грязная".
Справочники - как правило, очень часто запрашиваемая часть данных системы. Поэтому каждая операция в классах, реализующих интерфейс IApplicationPeer, агрессивно кэширует данные. Операции также следят за тем, чтобы кэш корректно очищался при изменении записей в административном интерфейсе.
Операция IApplicationPeer::retrieveListByFields() возвращает элементы справочника, отсортированные тем или иным способом. Однако порядок сортировки никак не специфицируется интерфейсом IApplicationPeer и полностью задается приложением. Как правило, порядок сортировки в справочниках определяется специальным полем sort, не входящим в детерминант справочника. Однако возможны ситуации, когда необходимы другие, более сложные критерии сортировки (например, сортировка по имени улицы). Операция retrieveListByFields() должна обрабатывать все такие ситуации внутри себя.
Существуют две противоположные схемы модерации справочников.
Нужно заметить, что метод (2) на практике оказывается в несколько раз сложнее, чем метод (1). Вот причины этого:
Таким образом, вариант (1) модерации прямо на продакшен-сервере представляется значительно более выигрышным. К его недостаткам можно отнести разве что некоторое увеличение опасности при редактировании данных (можно случайно испортить много элементов справочника), однако эта проблема решается введением двух ролей - "младшего" и "старшего" администраторов. Вариант (1) также исключает необходимость опасной процедуры миграции данных между различными зонами, что улучшает безопасность системы в целом и делает ее проще.
![]() |
|
Дмитрий Котеров |
25 января 2008 г.
©1999-2018
|
|
Вернуться к оглавлению |