Статьи

Несколько случаев, когда моделирование EAV действительно работает в LedgerSMB.

Моделирование сущности-атрибута-значения (EAV) обычно рассматривается как антипаттерн и по уважительным причинам. Неправильное применение приводит к негибким запросам, низкой производительности и ряду других неприятностей. Тем не менее, есть классы проблем, где EAV является правильным решением для рассматриваемой проблемы. Это продемонстрируют две конкретные области LedgerSMB, а именно управление учетными записями / выпадающими списками и схема меню. Они на самом деле представляют разные классы проблем, но у них есть много общего. Цель этого поста — помочь читателям лучше понять, где моделирование EAV может оказать большую помощь.

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

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

Что такое EAV?

EAV — это способ моделирования данных относительно свободной формы таким образом, чтобы избежать необходимости в чрезвычайно сложных, длинных таблицах для определения разреженных данных. Типичный подход EAV имеет три основные таблицы, представляющие данные объекта, атрибуты и данные значения.

Для успешной работы EAV в таблицу сущностей или родительские / дочерние таблицы следует поместить как можно больше данных. Только когда этот подход нарушается, EAV — хорошая идея. Приведенные ниже примеры конкретизируют это подробнее. Однако базовый типичный дизайн может выглядеть так:

CREATE TABLE entity (
    id serial not null unique,
    label text primary key,
    ....
);

CREATE TABLE attribute (
   id serial not null unique,
   att_name text not null,
   att_datatype text not null,
   ....
);

CREATE TABLE value_table (
   id bigserial not null unique,
   entity_id int not null references entity(id),
   attribute_id int not null references attribute(id),
   value text not null
);

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

Почему EAV?

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

По сути, EAV может иногда (!) Быть полезным подходом для использования, когда данные плохо вписываются в другие модели. К сожалению, когда его хватают за слишком быстрый результат, это плохо, но это не мешает ему быть полезным в тех случаях, когда он дома.

Почему не EAV?

EAV, неправильно примененный, приводит к значительному количеству проблем. Неправильное применение приводит к невозможности декларативной проверки данных в схеме самой базы данных за пределами очень простых правил. Неправильное применение приводит к проблемам с производительностью и основным проблемам управления данными.

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

Измененный EAV в схеме управления аккаунтом / выпадающим списком

В LedgerSMB план счетов прилагается к выпадающим спискам. Схема выглядит так:

CREATE TABLE account (
  id serial not null unique,
  accno text primary key,
  description text,
  category CHAR(1) NOT NULL,
  gifi_accno text,
  heading int not null references account_heading(id),
  contra bool not null default false,
  tax bool not null default false
);


CREATE TABLE account_link_description (
    description text    primary key,
    summary     boolean not null,
    custom      boolean not null
);

CREATE TABLE account_link (
   account_id int references account(id),
   description text references account_link_description(description),
   primary key (account_id, description)
);

Теперь, что явно упускается здесь, в перспективе EAV, это значения. У нас есть сущности и атрибуты, но наличие самого атрибута является ценностью. Это было бы как EAV с просто логическими значениями.

Архитектура ставит все классические проблемы, которые есть у EAV. Существуют правила относительно того, какие комбинации относительно каких комбинаций раскрывающихся списков являются недействительными. Это не сложные правила, но они могут измениться со временем. В настоящее время, если account_link является сводной учетной записью, тогда допускается только одна раскрывающаяся ссылка, поэтому другие не могут существовать. Пользовательские аккаунты не проверены и могут нарушать эти правила.

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

Обычно таблицы, используемые здесь, маленькие. Типичный бизнес будет иметь не более нескольких десятков записей аккаунта, а может быть, сто или две записи account_link, и есть только 27 описаний ссылок аккаунта. Однако более крупные компании могут иметь тысячи учетных записей и десятки тысяч записей account_link.

Подход здесь хорошо масштабируется, потому что есть только три типичных шаблона поиска. 

Первый — это поиск аккаунтов по конкретному описанию ссылки (выпадающий список). Это обычно тянет только часть счетов и может управляться через индексы.

Второй — это поиск по account_links для данного аккаунта. Это тривиально и обычно не более 20 предметов.

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

Важно отметить, что данные плана счетов довольно стабильны. Это может изменить несколько записей с течением времени, но радикальные изменения или массовые обновления очень редки. Таким образом, мы можем пожертвовать некоторой немедленной производительностью в течение длительного времени.

 

Облегченный EAV в схеме определения меню

Наша схема меню выглядит следующим образом:

CREATE TABLE menu_node (
    id serial NOT NULL,
    label character varying NOT NULL,
    parent integer,
    "position" integer NOT NULL
);

CREATE TABLE menu_attribute (
    node_id integer NOT NULL references menu_node(id),
    attribute character varying NOT NULL,
    value character varying NOT NULL,
    id serial NOT NULL,
    primary key(node_id, attribute)
);

CREATE TABLE menu_acl (
    id serial NOT NULL,
    role_name character varying,
    acl_type character varying,
    node_id integer,
    CONSTRAINT menu_acl_acl_type_check CHECK ((((acl_type)::text = 'allow'::text) OR ((acl_type)::text = 'deny'::text))),
    PRIMARY KEY (node_id, role_name)
);

Здесь не указана таблица атрибутов. По сути, наша модель меню не проверяет данные, а данные чрезвычайно стабильны. Ожидается, что разработчик проверяет соответствие приложения.

Таблицы здесь относительно небольшие. По умолчанию пара сотен строк в menu_node и почти тысяча в menu_attribute. Эта структура растягивает то, что реляционные БД позволят вам делать изящно, поскольку menu_node представляет древовидную иерархию, а остальное устанавливает очень открытую (не проверенную) модель EAV.

Шаблоны доступа здесь, как правило, один раз для каждого входа в систему, извлекают все данные из этих таблиц (два сканирования, один для разрешений), а затем объединяют атрибуты так, что мы получаем достаточно информации для создания структуры меню (теги <ul> с гиперссылками внутри <li > теги).

Пункты меню обычно создаются только один раз и являются чрезвычайно стабильными. 

Выводы

Эти варианты использования имеют ряд поразительных общих черт. Шаблоны доступа просты, как и правила проверки данных в db (если они вообще существуют). Данные редко пишутся.

Второй момент — это нечто более высокое, что объединяет эти случаи. Они представляют собой способ сохранения приложением своего собственного API в базе данных. Другими словами, они представляют собой способ хранения информации о конфигурации приложения, или перехваты или ссылки на логику приложения (но не саму логику). Эти области семантически очень гибки, но, как правило, данные становятся долгоживущими после того, как все настроено.