При проектировании базы данных важно не только создать таблицы и индексы, но и отразить в них бизнес-логику.

Содержание:
1. Зачем документировать процесс проектирования БД.
2. От бизнес-требований к логической модели: что записывать.
3. Переход к физической модели: как документировать компромиссы.
4. Кейс: документирование БД сервиса подписок.
5. Инструменты для поддержания документации в актуальном состоянии.
6. Заключение.
1. Зачем документировать процесс проектирования БД
Первое, что нужно помнить:
Бизнес-требования ≠ схема БД
Документация связывает бизнес-требования и техническую реализацию. Без нее:
● Разработчики тратят время на reverse engineering.
● Оптимизации превращаются в костыли.
● Изменения в БД становятся рискованными.
Рассмотрим на примере (пример №1), когда заказчик говорит: «У пользователя может быть только одна активная подписка». Разработчик создает таблицу subscription с полем is_active.
Что забыли учесть?
● Поле is_active не гарантирует уникальность. Почему не добавили триггер или ограничение?
● Как обрабатывать отмену подписки? Нужно ли архивировать данные?
Через год новый разработчик добавляет подписки через INSERT, не проверяя is_active. В итоге у пользователя оказываются две «активные» подписки — баг, который могли бы предотвратить, если бы документация объясняла бизнес-правило.
Еще один пример (пример №2). Требование заказчика: «Пользователь может сменить тариф, но мы должны сохранять историю изменений для аналитики». Разработчик создает таблицы subscription и tariff и добавляет в subscriptions поле tariff_id.
Что пошло не так?
● История изменений нигде не записывается.
Итог:
● Через месяц аналитики просят отчет «как часто меняли тарифы», но данных нет.
● Приходится экстренно править схему, переносить данные.
Чтобы определить, что должно быть в документации для процесса проектирования БД, начнем постепенно заполнять чек-лист.
Что должно быть задокументировано |
Пример |
Бизнес-сущности и их атрибуты |
Подписка (subscription) — это доступ пользователя к сервису на определенный тариф. Имеет дату начала/окончания. |
Ограничения |
У пользователя может быть только одна активная подписка (проверяется триггером check_single_active_subscription). |
Причины денормализации |
Поле product_name дублируется в order_items, чтобы сохранять историческое название товара. |
Сложные связи |
Таблица tariff связана с tariff_history, но данные в нее попадают через триггер, а не API. |
2. От бизнес-требований к логической модели: что записывать
Перейдем к логической модели. Если представить бизнес-требования как рассказ, то логическая модель — это его грамотный технический пересказ без искажений. Ее главная задача — сохранить смысл требований в структуре БД и четко зафиксировать, почему она устроена именно так.
Ключевые элементы документирования логической модели включают следующее:
● ER-диаграмма,
● глоссарий,
● принятые решения,
● примеры данных (опционально).
Ниже приведено описание применения этих способов документирования для примера из предыдущего раздела (пример №2).
2.1. ER-диаграммы с пояснениями
Сначала определяются основные сущности по результатам анализа предметной области и бизнес-требований. По ним формируются таблицы, например, user и tariff. Далее проектируются связи между таблицами, на этом этапе могут возникнуть связующие таблицы. Так, для реализации требования хранить несколько изменений в тарифах и даты, когда они произошли, создается таблица tariff_history.
Для реализации нефункциональных требований также могут быть добавлены технические таблицы, например, с логами.
После визуализации результатов анализа, описанного выше, схема проектируемой БД воплощается в виде ER-диаграммы.
Инструменты: DBML, Mermaid, Lucidchart.
2.2. Глоссарий терминов
Для синхронизации терминов и определений на проекте формируется перечень ключевых понятий. Он помогает всем участникам проекта говорить на одном языке.
Список терминов формируется из интервью с заказчиком и из бизнес-требований. Под проект выбирается структура глоссария, для сложных проектов его можно разделить на две таблицы: термины с определениями и справочник таблиц и полей.
Глоссарий дополняет ER-диаграмму. Когда ER-диаграмма показывает связи, глоссарий поясняет значение сущностей в бизнес-контексте.
2.3. Принятые решения
В процессе проектирования систем и БД часто рассматриваются разные варианты реализаций, а затем по определенным причинам выбор делает в пользу одной из них. Важно записать, что повлияло на решение, чтобы не проводить повторные анализы, не потерять контекст и не вернуть случайно ранее отвергнутые варианты.
Принятые решения должны быть описаны в документации, если их появлению предшествовали совещания по проблемным вопросам, необходимость прийти к компромиссам или технические ограничения реализаций.
Пример документирования альтернатив, которые отвергли, и почему:
«Не стали использовать триггер для автоматического создания записей в tariff_change, так как это усложняет отладку. Изменения вносятся явно через API».
Ниже небольшой чек-лист для самопроверки при документировании логической модели.
Что должно быть в документации логической модели |
Пример |
ER-диаграммы |
Пояснение к схеме: |
Глоссарий |
Термин: Смена тарифа. |
Список принятых решений |
Вариант: Проверять активную подписку через триггер, а не ограничение с условием. |
Примеры данных |
INSERT INTO tariff (tariff_id, tariff_name, tariff_price, is_active) |
3. Переход к физической модели: как документировать компромиссы
После разработки фундамента, логической модели, разрабатываются решения, переводящие ее на язык конкретной СУБД. Эти решения формируют физическую модель, которая фиксирует каждое отклонение от нормализованной структуры с учетом производительности (индексы, денормализация).
Без документации такие решения забываются. Например, в проекте онлайн-магазина электроники через полгода никто не помнит:
● Почему в таблице orders дублируется customer_name.
● Зачем добавлен «лишний» индекс.
● Почему данные хранятся в неоптимальной форме.
Результат — разработчики боятся вносить изменения, а оптимизации превращаются в технический долг. Чтобы избежать потери контекста, каждое неочевидное решение рекомендуется фиксировать в трёх ключевых точках:
1. Код/миграции (SQL-комментарии) — для разработчиков, работающих напрямую с БД.
2. Техническая документация (README/Confluence) — общий архитектурный контекст.
3. Схема данных (ER-диаграммы, описания таблиц) — визуальное отражение решений.
Пример №3: Документирование денормализации
Ситуация:
В идеальной схеме цена товара должна браться из таблицы products. Но возможны случаи, когда цена товара изменилась, а заказ должен сохранить старую стоимость. Для этого в order_items добавляется fixed_price.
Как и где документировать:
1. В SQL-скрипте миграции:
ALTER TABLE order_items ADD COLUMN fixed_price DECIMAL(10,2);
-- Цена на момент заказа. Дублируется из products.price для:
-- 1. Исторической точности (если цена товара изменится).
-- 2. Производительности (избегаем JOIN в отчетах).
2. В README репозитория:
Денормализация в order_items
Поле fixed_price хранит цену товара на момент заказа.
Причины
- Гарантия исторической корректности (цена в заказе не изменится, даже если обновится products.price).
- Ускорение отчетов (исключаем JOIN с products).
Риски:
- Необходимо обновлять поле при изменении цены (если заказ еще не завершен).
3. На схеме (например, Mermaid).
Пример №4: Обоснование индексов
Ситуация:
Добавлен составной индекс (user_id, created_at) в таблицу orders.
Как и где документировать:
1. В SQL-комментарии:
CREATE INDEX idx_user_created ON orders (user_id, created_at);
-- Ускоряем фильтрацию по пользователю + дате (отчеты в ЛК).
-- Без индекса: 1200 мс, с индексом: 50 мс.
2. В документации (Confluence/Notion):
Индекс |
Поля |
Назначение |
Эффективность |
idx_user_created |
user_id, created_at |
Оптимизация запросов «заказы пользователя за период» |
Снижение времени с 1200 мс до 50 мс |
3. На схеме (например, Mermaid).
Пример №5: JSON-поля вместо реляционной структуры
Ситуация:
Поле metadata в таблице user хранит динамические атрибуты (например, источник регистрации).
Как и где документировать:
1. В SQL-скрипте миграции:
ALTER TABLE users ADD COLUMN metadata JSONB;
-- Хранит динамические атрибуты (referral_source, promo_flags).
-- Не выносим в отдельные таблицы, так как:
-- 1. Структура меняется части (не требует ALTER TABLE).
-- 2. Нет запросов с фильтрацией по этим полям.
2. Страница в Confluence/Notion — для сложных решений.
Чтобы систематизировать подход, рекомендуется использовать чек-лист для проверки полноты документации. Вот ключевые категории, которые стоит учитывать:
Что должно быть задокументировано |
Пример |
Архитектурные решения | |
Репликация |
Чтение из реплик для отчетов, запись только в мастер. |
Шардирование |
Данные пользователей шардированы по user_id для горизонтального масштабирования. |
Решения по оптимизации производительности | |
Партиционирование |
Таблица orders партиционирована по created_at для ускорения архивных запросов. |
Оптимизация запросов |
Индекс (user_id, status) ускоряет фильтрацию по активным пользователям. |
Административные решения | |
Резервное копирование |
Ежедневные снепшоты с retention 7 дней. |
Для каждого «неидеального» решения рекомендуется указывать:
● причину (производительность, гибкость, требования),
● альтернативы (что отвергли и почему),
● последствия.
4. Кейс: Документирование БД сервиса подписок
Рассмотрим практический пример проектирования БД для сервиса подписок — от бизнес-требований до физической реализации.
Бизнес-требования:
1. Одна активная подписка на пользователя:
Пользователь должен иметь одну активную подписку.
2. История изменений тарифов:
При смене тарифа должна сохраняться старая цена для аудита и аналитики.
3. Аудит действий:
Система должна фиксировать изменений подписки (создание, продление, отмена) с указанием автора действия.
Логическая модель
ER-диаграмма в Mermaid:

Пояснения к диаграмме:
1. История изменений тарифов
Таблица tariff_history хранит только изменения цены, а не весь тариф, чтобы избежать избыточности.
2. События подписок
Таблица subscription_change фиксирует все действия (создание, отмена, изменение тарифа) для аудита.
Глоссарий терминов:
Термин |
Определение |
Источник |
Таблица БД |
Подписка |
Доступ пользователя к сервису |
Из бизнес-требования: «Пользователи должны иметь возможность подписываться на сервис» |
subscription |
Тариф |
План с ценой и условиями |
Из интервью с Заказчиком |
tariff |
Историчность |
Правило хранить все изменения цены |
Из бизнес-требования: «При смене тарифа должна сохраняться старая цена для аудита и аналитики» |
tariff_history |
Физическая модель
1. Денормализация цены в подписку
Проблема:
Цена тарифа может измениться, но стоимость активной подписки должна оставаться неизменной.
Решение:
Добавляем поле current_price в таблицу subscription, дублируя данные из tariff.
Документирование:
ALTER TABLE subscription ADD COLUMN current_price DECIMAL(10,2);
-- Дублирует tariff.price для:
-- 1. Исторической корректности (цена фиксируется на момент покупки).
-- 2. Производительности (исключаем JOIN в отчетах).
-- Обновляется триггером при смене тарифа.
2. Триггер для проверки активной подписки
Бизнес-правило: У пользователя может быть только одна активная подписка.
Реализация: через SQL-скрипты.
CREATE OR REPLACE FUNCTION check_active_subscription()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_active = TRUE AND EXISTS (
SELECT 1 FROM subscription
WHERE user_id = NEW.user_id
AND is_active = TRUE
AND subscription_id != NEW.subscription_id -- Исключаем текущую запись при обновлении
) THEN
RAISE EXCEPTION 'У пользователя может быть только одна активная подписка';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Триггер для INSERT/UPDATE
CREATE TRIGGER trg_check_active_subscription
BEFORE INSERT OR UPDATE ON subscription
FOR EACH ROW EXECUTE FUNCTION check_active_subscription();
Пояснение в документации:
Важно: триггер гарантирует соблюдение бизнес-правила. При попытке создать вторую активную подписку возникнет ошибка.
Альтернативы:
● Проверка на уровне приложения (риск: race condition).
● Ограничение через частичный индекс (сложность поддержки).
3. История изменений тарифов
Решение:
CREATE TABLE tariff_history (
history_id SERIAL PRIMARY KEY,
tariff_id INTEGER NOT NULL REFERENCES tariff(tariff_id),
old_price DECIMAL(10,2) NOT NULL, -- Цена до изменения
new_price DECIMAL(10,2) NOT NULL, -- Цена после изменения
changed_at TIMESTAMP NOT NULL DEFAULT NOW(),
changed_by INTEGER REFERENCES user(user_id)
);
-- Комментарий:
-- Хранит ТОЛЬКО изменения цены.
Почему не JSON?
● Структура данных предсказуема (цена + метаданные).
● Упрощает аналитические запросы (фильтрация по дате/пользователю).
Итог
1. Бизнес-правила зафиксированы в:
● SQL-комментариях (триггеры, ограничения).
● Отдельном документе (database_schema.md).
2. Компромиссы объяснены:
● Почему выбрана денормализация.
● Почему история тарифов — отдельная таблица.
3. Примеры запросов ускоряют onboarding новых разработчиков.
5. Инструменты для поддержания документации в актуальном состоянии
Документирование информации эффективно только в том случае, когда в документации содержится актуальная информация. Есть два основных подхода, которые могут помочь в поддержании ее актуальности.
5.1 Автоматическая генерация документации
Ручное обновление схем и описаний неизбежно приводит к расхождениям между документацией и фактической структурой БД. Современные инструменты позволяют автоматизировать этот процесс.
● Встроенные средства PgAdmin позволяют экспортировать актуальную структуру таблиц с типами полей, ограничения, индексы, а также SQL-комментарии к объектам БД.
● Визуализация: позволяет описывать схему в декларативном формате.
5.2 Changelog
Это еще один вариант поддерживать документацию в актуальном состоянии. Например, ведение migrations.md в репозитории с изменениями. А для сложных изменений добавление ссылки на задачи (тикеты) или обсуждения.
В итоге, инструменты помогут сделать документацию более полезной:
● Автогенерация сэкономит время и снизит риск расхождений.
● Changelog сохранит контекст изменений для всей команды.
6. Заключение
Проектирование БД — это не только создание таблиц и индексов, но и документирование решений, чтобы разработчики понимали, почему схема устроена именно так, аналитики могли самостоятельно писать запросы, а новые члены команды быстрее входили в проект.
Ключевые шаги:
● Записывайте бизнес-правила в логической модели (ER-диаграммы, глоссарий).
● Объясняйте компромиссы в физической реализации (SQL-комментарии, README).
● Автоматизируйте поддержание документации в актуальном состоянии.
Итог:
Документация — это живой инструмент. Если она встраивается в процесс разработки, то помогает сохранить целостность проекта на всем его жизненном цикле.
Akina
То, что активная подписка - это атрибут сущности пользователь, а не атрибут сущности подписка. То есть разработчик накосячил. Например, он должен был в таблицу пользователей добавить поле active_subscription, которое является внешней ссылкой на таблицу подписок.
Ну так опять разработчик накосорезил... Просто проигнорировал требования заказчика. Гоните его в шею.
Дальше даже не смотрел...