Передо мной стояла задача разработать универсальный каталог товаров и услуг, а также каталог предприятий, документов и всего остального.
В моей работе этот «опыт» не пригодился, но идея хорошая, по моему скромному мнению :) Хотелось бы поделиться и выслушать критику.
Каталог подразумевает упорядочивание — иерархию, подразумевает непосредственное хранение информации и, конечно, поиск, наверное, аналитику… что-то еще? Больше ничего не приходит на ум.
Теперь по пунктам.
Иерархия
Однозначно группировка информации будет древовидной, от абстрактного «Каталога» к более конкретному и более конкретному (например, «молоток»).Уровень детализации может быть любым, не будем держать себя в рамках «раздела», «подраздела», «категории», «подкатегории», пусть глубина ветвления будет бесконечной.
Информация будет храниться в какой-то СУБД; для работы с иерархией данная СУБД должна уметь выполнять иерархические запросы; таких СУБД довольно много; самый популярный из бесплатных — 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 #Системный анализ и проектирование-
Курс Монеты Ethereum: Проблемы И Перспективы
19 Oct, 24 -
Шпаргалки По Интернет-Сервисам
19 Oct, 24 -
Курение И Книги
19 Oct, 24 -
Особенности Игрового Рынка Китая – Часть 1
19 Oct, 24