Идеальный Каталог, Архитектурный Эскиз

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

В моей работе этот «опыт» не пригодился, но идея хорошая, по моему скромному мнению :) Хотелось бы поделиться и выслушать критику.

Каталог подразумевает упорядочивание — иерархию, подразумевает непосредственное хранение информации и, конечно, поиск, наверное, аналитику… что-то еще? Больше ничего не приходит на ум.

Теперь по пунктам.



Иерархия

Однозначно группировка информации будет древовидной, от абстрактного «Каталога» к более конкретному и более конкретному (например, «молоток»).

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

Информация будет храниться в какой-то СУБД; для работы с иерархией данная СУБД должна уметь выполнять иерархические запросы; таких СУБД довольно много; самый популярный из бесплатных — PostgreSQL. DDL-признаки Дерево элементов:

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

CREATE TABLE element_tree ( id SERIAL PRIMARY KEY NOT NULL, element_tree_id INTEGER, is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_element_tree FOREIGN KEY (element_tree_id) REFERENCES element_tree (id) ); CREATE INDEX ix_element_tree_element_tree_id_id ON element_tree (element_tree_id, id);



Пояснения к структуре таблицы

id — идентификатор строки таблицы.

element_tree_id — ссылка на родительский элемент дерева.

is_hidden - флаг удаленной записи (0 - действительная запись, 1 - удалена), почему флаг поднимается только для записи, а не для удаления? потому что когда вы встречаете в логах идентификатор и хотите посмотреть, что это такое, то очень удобно делать выборку в текущей базе, а не делать эту выборку в резервной копии (и не факт, что эта запись будет найден в этой резервной копии).

Insert_date — дата добавления записи, удобно, когда вы знаете, является ли это запись 100-летней давности или она была вставлена пять минут назад в результате неудачной вставки.

Конечно, людям, знакомым с системой, все это не особо нужно (is_hidden,insert_date), но для тех, кто смотрит на систему как на таран на новые ворота, эти поля будут очень полезны, в своей практике я обычно в роли барана :) CONSTRAINT fk_element_tree — внешний ключ самого себя — указатель на родительский элемент. INDEX ix_element_tree_element_tree_id_id — индекс для поиска дочерних ветвей (узлов-потомков); если мы находим родителя по первичному ключу, то для ускорения поиска потомков нам нужно создать отдельный индекс.

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

И почему? А поскольку иерархия - это всего лишь иерархия, и упорядочены не узлы дерева, а те таблицы, которые к иерархии пристыкованы, поэтому имена находятся в таблицах, а в иерархии есть только группировка элементы.



Прямое хранение информации

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

Рубрика — это группа объектов с уникальным набором характеристик.

То есть у нас есть связь между самими Сущностями и сгруппированными сущностями - Рубриками, а кроме того, Рубрика - это еще и группировка по уникальным Характеристикам этих Сущностей.

То есть информация разделена на три части – Сущность, Характеристики, Рубрика, где Рубрика – это точка соединения нескольких Сущностей и Характеристик.

На языке СУБД это звучит так: Таблица заголовков

CREATE TABLE rubric ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_rubric_code ON rubric (code);

Таблица сущностей (некая Вещь, которая может оказаться продуктом, услугой или компанией, или даже отчетом и чем-то еще):

CREATE TABLE item ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_item_code ON item (code);

Характеристики таблицы (свойства):

CREATE TABLE property ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_property_code ON property (code);

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

title — имя (имя пришлось заменить на title, поскольку имя — ключевое слово для PostgreSql).

описание - описание (имя используется для выбора в списке, а описание используется для собственно описания цели записи).

Теперь поговорим о том, как все это связано.



Организация информации в каталоге

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

CREATE TABLE rubric_element_tree ( id SERIAL PRIMARY KEY NOT NULL, rubric_id INTEGER NOT NULL, element_tree_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_rubric_element_tree_rubric_id FOREIGN KEY (rubric_id) REFERENCES rubric (id), CONSTRAINT fk_rubric_element_tree_element_tree_id FOREIGN KEY (element_tree_id) REFERENCES element_tree (id) ); CREATE UNIQUE INDEX ux_rubric_element_tree_rubric_id ON rubric_element_tree (rubric_id); CREATE UNIQUE INDEX ux_rubric_element_tree_element_tree_id ON rubric_element_tree (element_tree_id);

Таблица действует как отношение «один к одному», таблица имеет два внешних ключа, и каждый столбец ключа имеет свой собственный индекс.

И Рубрику, и элемент дерева можно соединить только один раз, поэтому для каждого столбца создается индекс уникальности.

Каждый раздел имеет свой набор Характеристик (свойств):

CREATE TABLE rubric_property ( id SERIAL PRIMARY KEY NOT NULL, rubric_id INTEGER NOT NULL, property_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_rubric_property_rubric_id FOREIGN KEY (rubric_id) REFERENCES rubric (id), CONSTRAINT fk_rubric_property_property_id FOREIGN KEY (property_id) REFERENCES property (id) ); CREATE UNIQUE INDEX ux_rubric_property_rubric_id ON rubric_property (rubric_id, property_id); CREATE INDEX ix_rubric_property_property_id ON rubric_property (property_id);

Таблица имеет два внешних ключа, отношение один-ко-многим.

Одна Рубрика имеет одну Характеристику один раз – она обеспечивается индексом; разные Рубрики могут иметь одну и ту же Характеристику - индекс по Характеристике без уникальных значений.

Каждая категория имеет свой набор Сущностей (Частей):

CREATE TABLE rubric_item ( id SERIAL PRIMARY KEY NOT NULL, rubric_id INTEGER NOT NULL, item_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_rubric_item_rubric_id FOREIGN KEY (rubric_id) REFERENCES rubric (id), CONSTRAINT fk_rubric_item_item_id FOREIGN KEY (item_id) REFERENCES item (id) ); CREATE UNIQUE INDEX ux_rubric_item_rubric_id_item_id ON rubric_item (rubric_id, item_id); CREATE UNIQUE INDEX ux_rubric_item_item_id ON rubric_item (item_id);

Таблица имеет два внешних ключа, отношение один-ко-многим.

Одна Рубрика имеет несколько Сущностей; каждая Сущность может принадлежать только одной Рубрике.

Это была структура хранения информации, а где сама информация? Сама информация хранится отдельно.



Хранилище данных

Табличные значения (значение информационной характеристики):

CREATE TABLE content ( id SERIAL PRIMARY KEY NOT NULL, raw VARCHAR(4000), redactor_id INTEGER NOT NULL, property_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_content_redactor_id FOREIGN KEY (redactor_id) REFERENCES redactor (id), CONSTRAINT fk_content_property_id FOREIGN KEY (property_id) REFERENCES property (id) ); CREATE INDEX ix_content_redactor_id ON content (redactor_id); CREATE INDEX ix_content_property_id ON content (property_id);

Эта пластина не совсем обычная, по сути это просто ячейка «памяти», хранящая значение (необработанное).

Значение определенного свойства (property_id).

Значение, указанное конкретным редактором (redactor_id).

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

CREATE TABLE redactor ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_redactor_code ON redactor (code);

Зачем нам нужен редактор? Информационный каталог должен был быть чем-то вроде Википедии, где каждый Редактор мог установить свой вариант значений Характеристик для каждой Сущности.

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

Таблица Значения хранит только строковое представление информации о Характеристике.

Это фактический ввод пользователя.

Система работает с разным представлением этой информации, причем представление зависит от типа данных.

Каждому типу данных соответствует своя таблица.

Струны

CREATE TABLE string_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, string VARCHAR(4000), insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_string_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_string_matter_content_id ON string_matter (content_id);

*для хранения строк в PostgreSql следует использовать ТЕКСТ Числа

CREATE TABLE digital_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, digital DOUBLE PRECISION, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_digital_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_digital_matter_content_id ON digital_matter (content_id);

Даты (метки времени)

CREATE TABLE date_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, date_time TIMESTAMP WITH TIME ZONE insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_date_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_date_matter_content_id ON date_matter (content_id);

Временные интервалы

CREATE TABLE duration_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, duration INTERVAL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_duration_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_duration_matter_content_id ON duration_matter (content_id);

Типы данных были специально выбраны как «международные», чтобы структуру базы данных можно было перенести на любую платформу, в любую СУБД.

Название «материя» было выбрано из-за его созвучия со словами «материя» и «сущность».

И еще об одном я не говорил, это варианты:

CREATE TABLE option ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_option_code ON option (code);

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

Опции связаны с характеристиками:

CREATE TABLE property_option ( id SERIAL PRIMARY KEY NOT NULL, property_id INTEGER NOT NULL, option_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_property_option_property_id FOREIGN KEY (property_id) REFERENCES property (id), CONSTRAINT fk_property_option_option_id FOREIGN KEY (option_id) REFERENCES option (id) ); CREATE UNIQUE INDEX ux_property_option_property_id_option_id ON property_option (property_id, option_id); CREATE INDEX ix_property_option_option_id ON property_option (option_id);

Контент связан с сущностями:

CREATE TABLE item_content ( id SERIAL PRIMARY KEY NOT NULL, item_id INTEGER NOT NULL, content_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_item_content_item_id FOREIGN KEY (item_id) REFERENCES item (id), CONSTRAINT fk_item_content_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_item_content_item_id_content_id ON item_content (item_id, content_id); CREATE UNIQUE INDEX ux_item_content_content_id ON item_content (content_id);

Собственно, это все компоненты Информационного каталога.



Особенность «архитектуры»

Теперь самый главный вопрос: зачем так много связующих таблиц? Неужели нельзя напрямую соединить таблицы? Ответ в том, что такая «архитектура» нацелена на максимальную модульность.

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

Гибкость нарушается только таблицей Значений — содержанием.

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

Связь между содержанием и свойством жесткая, поскольку Значение (содержание) не может быть интерпретировано вне Характеристики (свойства).

Гибкость связей сделана для удобства переноса субъектов между другими субъектами Системы.

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

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

Мы можем использовать только строковое представление информации и забыть о узкоспециализированных представлениях в таблицах *_matter. Мы можем использовать только Рубрики, не разбрасывая Рубрики по Древу Стихий.

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

Мы можем добавлять или удалять Характеристики Рубрики, но на Значения это никак не повлияет. То есть от проекта к проекту мы можем использовать только тот функционал, который нужен, а то, что не нужно, можно в два счета вырезать, просто исключив ненужные классы из сборки.

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

С увеличением накладных расходов на доступ к данным мы получили большую гибкость и большую устойчивость к неосторожным действиям пользователей, можем проводить некоторые эксперименты без необходимости резервного копирования, для таких «рискованных» и ленивых программистов как я это большой плюс :) Помимо всей этой «красоты» есть еще и PHP-код, но об этом в следующий раз, а учитывая мой «Режим восстановления», только через неделю.

ПС.

Наверное, после ваших комментариев мне нужно будет еще раз написать об этой «архитектуре», и тогда я смогу рассказать о классах PHP для работы с этой системой хранения и обработки данных.



Диаграмма ER



Идеальный каталог, архитектурный эскиз



Продолжение

Идеальный каталог, архитектурный эскиз Теги: #сборка велосипедов #архитектура системы #Аномальное программирование #postgresql #Системный анализ и проектирование


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

В моей работе этот «опыт» не пригодился, но идея хорошая, по моему скромному мнению :) Хотелось бы поделиться и выслушать критику.

Каталог подразумевает упорядочивание — иерархию, подразумевает непосредственное хранение информации и, конечно, поиск, наверное, аналитику… что-то еще? Больше ничего не приходит на ум.

Теперь по пунктам.



Иерархия

Однозначно группировка информации будет древовидной, от абстрактного «Каталога» к более конкретному и более конкретному (например, «молоток»).

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

Информация будет храниться в какой-то СУБД; для работы с иерархией данная СУБД должна уметь выполнять иерархические запросы; таких СУБД довольно много; самый популярный из бесплатных — PostgreSQL. DDL-признаки Дерево элементов:

CREATE TABLE element_tree ( id SERIAL PRIMARY KEY NOT NULL, element_tree_id INTEGER, is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_element_tree FOREIGN KEY (element_tree_id) REFERENCES element_tree (id) ); CREATE INDEX ix_element_tree_element_tree_id_id ON element_tree (element_tree_id, id);



Пояснения к структуре таблицы

id — идентификатор строки таблицы.

element_tree_id — ссылка на родительский элемент дерева.

is_hidden - флаг удаленной записи (0 - действительная запись, 1 - удалена), почему флаг поднимается только для записи, а не для удаления? потому что когда вы встречаете в логах идентификатор и хотите посмотреть, что это такое, то очень удобно делать выборку в текущей базе, а не делать эту выборку в резервной копии (и не факт, что эта запись будет найден в этой резервной копии).

Insert_date — дата добавления записи, удобно, когда вы знаете, является ли это запись 100-летней давности или она была вставлена пять минут назад в результате неудачной вставки.

Конечно, людям, знакомым с системой, все это не особо нужно (is_hidden,insert_date), но для тех, кто смотрит на систему как на таран на новые ворота, эти поля будут очень полезны, в своей практике я обычно в роли барана :) CONSTRAINT fk_element_tree — внешний ключ самого себя — указатель на родительский элемент. INDEX ix_element_tree_element_tree_id_id — индекс для поиска дочерних ветвей (узлов-потомков); если мы находим родителя по первичному ключу, то для ускорения поиска потомков нам нужно создать отдельный индекс.

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

И почему? А поскольку иерархия - это всего лишь иерархия, и упорядочены не узлы дерева, а те таблицы, которые к иерархии пристыкованы, поэтому имена находятся в таблицах, а в иерархии есть только группировка элементы.



Прямое хранение информации

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

Рубрика — это группа объектов с уникальным набором характеристик.

То есть у нас есть связь между самими Сущностями и сгруппированными сущностями - Рубриками, а кроме того, Рубрика - это еще и группировка по уникальным Характеристикам этих Сущностей.

То есть информация разделена на три части – Сущность, Характеристики, Рубрика, где Рубрика – это точка соединения нескольких Сущностей и Характеристик.

На языке СУБД это звучит так: Таблица заголовков

CREATE TABLE rubric ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_rubric_code ON rubric (code);

Таблица сущностей (некая Вещь, которая может оказаться продуктом, услугой или компанией, или даже отчетом и чем-то еще):

CREATE TABLE item ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_item_code ON item (code);

Характеристики таблицы (свойства):

CREATE TABLE property ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_property_code ON property (code);

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

title — имя (имя пришлось заменить на title, поскольку имя — ключевое слово для PostgreSql).

описание - описание (имя используется для выбора в списке, а описание используется для собственно описания цели записи).

Теперь поговорим о том, как все это связано.



Организация информации в каталоге

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

CREATE TABLE rubric_element_tree ( id SERIAL PRIMARY KEY NOT NULL, rubric_id INTEGER NOT NULL, element_tree_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_rubric_element_tree_rubric_id FOREIGN KEY (rubric_id) REFERENCES rubric (id), CONSTRAINT fk_rubric_element_tree_element_tree_id FOREIGN KEY (element_tree_id) REFERENCES element_tree (id) ); CREATE UNIQUE INDEX ux_rubric_element_tree_rubric_id ON rubric_element_tree (rubric_id); CREATE UNIQUE INDEX ux_rubric_element_tree_element_tree_id ON rubric_element_tree (element_tree_id);

Таблица действует как отношение «один к одному», таблица имеет два внешних ключа, и каждый столбец ключа имеет свой собственный индекс.

И Рубрику, и элемент дерева можно соединить только один раз, поэтому для каждого столбца создается индекс уникальности.

Каждый раздел имеет свой набор Характеристик (свойств):

CREATE TABLE rubric_property ( id SERIAL PRIMARY KEY NOT NULL, rubric_id INTEGER NOT NULL, property_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_rubric_property_rubric_id FOREIGN KEY (rubric_id) REFERENCES rubric (id), CONSTRAINT fk_rubric_property_property_id FOREIGN KEY (property_id) REFERENCES property (id) ); CREATE UNIQUE INDEX ux_rubric_property_rubric_id ON rubric_property (rubric_id, property_id); CREATE INDEX ix_rubric_property_property_id ON rubric_property (property_id);

Таблица имеет два внешних ключа, отношение один-ко-многим.

Одна Рубрика имеет одну Характеристику один раз – она обеспечивается индексом; разные Рубрики могут иметь одну и ту же Характеристику - индекс по Характеристике без уникальных значений.

Каждая категория имеет свой набор Сущностей (Частей):

CREATE TABLE rubric_item ( id SERIAL PRIMARY KEY NOT NULL, rubric_id INTEGER NOT NULL, item_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_rubric_item_rubric_id FOREIGN KEY (rubric_id) REFERENCES rubric (id), CONSTRAINT fk_rubric_item_item_id FOREIGN KEY (item_id) REFERENCES item (id) ); CREATE UNIQUE INDEX ux_rubric_item_rubric_id_item_id ON rubric_item (rubric_id, item_id); CREATE UNIQUE INDEX ux_rubric_item_item_id ON rubric_item (item_id);

Таблица имеет два внешних ключа, отношение один-ко-многим.

Одна Рубрика имеет несколько Сущностей; каждая Сущность может принадлежать только одной Рубрике.

Это была структура хранения информации, а где сама информация? Сама информация хранится отдельно.



Хранилище данных

Табличные значения (значение информационной характеристики):

CREATE TABLE content ( id SERIAL PRIMARY KEY NOT NULL, raw VARCHAR(4000), redactor_id INTEGER NOT NULL, property_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_content_redactor_id FOREIGN KEY (redactor_id) REFERENCES redactor (id), CONSTRAINT fk_content_property_id FOREIGN KEY (property_id) REFERENCES property (id) ); CREATE INDEX ix_content_redactor_id ON content (redactor_id); CREATE INDEX ix_content_property_id ON content (property_id);

Эта пластина не совсем обычная, по сути это просто ячейка «памяти», хранящая значение (необработанное).

Значение определенного свойства (property_id).

Значение, указанное конкретным редактором (redactor_id).

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

CREATE TABLE redactor ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_redactor_code ON redactor (code);

Зачем нам нужен редактор? Информационный каталог должен был быть чем-то вроде Википедии, где каждый Редактор мог установить свой вариант значений Характеристик для каждой Сущности.

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

Таблица Значения хранит только строковое представление информации о Характеристике.

Это фактический ввод пользователя.

Система работает с разным представлением этой информации, причем представление зависит от типа данных.

Каждому типу данных соответствует своя таблица.

Струны

CREATE TABLE string_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, string VARCHAR(4000), insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_string_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_string_matter_content_id ON string_matter (content_id);

*для хранения строк в PostgreSql следует использовать ТЕКСТ Числа

CREATE TABLE digital_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, digital DOUBLE PRECISION, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_digital_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_digital_matter_content_id ON digital_matter (content_id);

Даты (метки времени)

CREATE TABLE date_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, date_time TIMESTAMP WITH TIME ZONE insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_date_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_date_matter_content_id ON date_matter (content_id);

Временные интервалы

CREATE TABLE duration_matter ( content_id INTEGER NOT NULL, id SERIAL PRIMARY KEY NOT NULL, duration INTERVAL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_duration_matter_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_duration_matter_content_id ON duration_matter (content_id);

Типы данных были специально выбраны как «международные», чтобы структуру базы данных можно было перенести на любую платформу, в любую СУБД.

Название «материя» было выбрано из-за его созвучия со словами «материя» и «сущность».

И еще об одном я не говорил, это варианты:

CREATE TABLE option ( id SERIAL PRIMARY KEY NOT NULL, code CHAR(100), title VARCHAR(4000), description VARCHAR(4000), is_hidden INTEGER DEFAULT 0, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE UNIQUE INDEX ux_option_code ON option (code);

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

Опции связаны с характеристиками:

CREATE TABLE property_option ( id SERIAL PRIMARY KEY NOT NULL, property_id INTEGER NOT NULL, option_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_property_option_property_id FOREIGN KEY (property_id) REFERENCES property (id), CONSTRAINT fk_property_option_option_id FOREIGN KEY (option_id) REFERENCES option (id) ); CREATE UNIQUE INDEX ux_property_option_property_id_option_id ON property_option (property_id, option_id); CREATE INDEX ix_property_option_option_id ON property_option (option_id);

Контент связан с сущностями:

CREATE TABLE item_content ( id SERIAL PRIMARY KEY NOT NULL, item_id INTEGER NOT NULL, content_id INTEGER NOT NULL, insert_date TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_item_content_item_id FOREIGN KEY (item_id) REFERENCES item (id), CONSTRAINT fk_item_content_content_id FOREIGN KEY (content_id) REFERENCES content (id) ); CREATE UNIQUE INDEX ux_item_content_item_id_content_id ON item_content (item_id, content_id); CREATE UNIQUE INDEX ux_item_content_content_id ON item_content (content_id);

Собственно, это все компоненты Информационного каталога.



Особенность «архитектуры»

Теперь самый главный вопрос: зачем так много связующих таблиц? Неужели нельзя напрямую соединить таблицы? Ответ в том, что такая «архитектура» нацелена на максимальную модульность.

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

Гибкость нарушается только таблицей Значений — содержанием.

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

Связь между содержанием и свойством жесткая, поскольку Значение (содержание) не может быть интерпретировано вне Характеристики (свойства).

Гибкость связей сделана для удобства переноса субъектов между другими субъектами Системы.

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

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

Мы можем использовать только строковое представление информации и забыть о узкоспециализированных представлениях в таблицах *_matter. Мы можем использовать только Рубрики, не разбрасывая Рубрики по Древу Стихий.

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

Мы можем добавлять или удалять Характеристики Рубрики, но на Значения это никак не повлияет. То есть от проекта к проекту мы можем использовать только тот функционал, который нужен, а то, что не нужно, можно в два счета вырезать, просто исключив ненужные классы из сборки.

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

С увеличением накладных расходов на доступ к данным мы получили большую гибкость и большую устойчивость к неосторожным действиям пользователей, можем проводить некоторые эксперименты без необходимости резервного копирования, для таких «рискованных» и ленивых программистов как я это большой плюс :) Помимо всей этой «красоты» есть еще и PHP-код, но об этом в следующий раз, а учитывая мой «Режим восстановления», только через неделю.

ПС.

Наверное, после ваших комментариев мне нужно будет еще раз написать об этой «архитектуре», и тогда я смогу рассказать о классах PHP для работы с этой системой хранения и обработки данных.



Диаграмма ER



Идеальный каталог, архитектурный эскиз



Продолжение

Идеальный каталог, архитектурный эскиз Теги: #сборка велосипедов #архитектура системы #Аномальное программирование #postgresql #Системный анализ и проектирование
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.