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

Содержание:
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-диаграммы

Пояснение к схеме:
- tariff_change связана с tariff, а не напрямую с user, чтобы избежать избыточности.
- Поле status в subscription проверяется триггером, а не ограничением, для гибкости.

Глоссарий

Термин: Смена тарифа.
Определение: Изменение тарифного плана в рамках подписки.
Пример в БД: таблица subscription_change.

Список принятых решений

Вариант: Проверять активную подписку через триггер, а не ограничение с условием.
Почему отказались: реализация с ограничением усложняет процесс модификации условий при изменении бизнес-требования и менее прозрачно для разработчиков при анализе проблем.

Примеры данных

INSERT INTO tariff (tariff_id, tariff_name, tariff_price, is_active)
VALUES (1, 'Базовый', 299.00, TRUE);

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).
● Автоматизируйте поддержание документации в актуальном состоянии.

Итог:

Документация — это живой инструмент. Если она встраивается в процесс разработки, то помогает сохранить целостность проекта на всем его жизненном цикле.

Комментарии (1)


  1. Akina
    23.06.2025 09:38

    Рассмотрим на примере (пример №1), когда заказчик говорит: «У пользователя может быть только одна активная подписка». Разработчик создает таблицу subscription с полем is_active.

    Что забыли учесть?

    То, что активная подписка - это атрибут сущности пользователь, а не атрибут сущности подписка. То есть разработчик накосячил. Например, он должен был в таблицу пользователей добавить поле active_subscription, которое является внешней ссылкой на таблицу подписок.

    Требование заказчика: «Пользователь может сменить тариф, но мы должны сохранять историю изменений для аналитики». Разработчик создает таблицы subscription и tariff и добавляет в subscriptions поле tariff_id.

    Что пошло не так?

    Ну так опять разработчик накосорезил... Просто проигнорировал требования заказчика. Гоните его в шею.

    Дальше даже не смотрел...