Описание базы данных у меня менялось от проекта к проекту — каждый раз я пытался улучшить подход. На одном проекте я вёл всю документацию по БД в dbdiagram. Схема выглядела красиво, но поддерживать её было тяжело, а главное — в ней не было места для бизнес-логики данных: что вычисляется, откуда берётся, при каких условиях заполняется. Голая структура без контекста.

На другом проекте я пошёл в обратную сторону — описывал только бизнесовые сущности, без технических деталей. Никаких связей между таблицами, никакой информации о том, что одна сущность в реальности разбита на несколько таблиц. Разработчики получали описание «Договор», а что за ним стоят три таблицы с разными ключами — додумывали сами. Вопросы сыпались постоянно.

В обоих случаях у меня была одна и та же проблема: писать требования к данным было мучительно. Приходилось дублировать информацию — то со схемы, то с других страниц вики. А дублирование всегда ведёт к одному: таблицы устаревают, связи теряются, ошибки множатся.

Со временем я выделил для себя три задачи, которые закрывают требования к данным: описание изменений в таблицах и маппинг, миграции существующих данных и описание данных вне БД (enum'ы). Это не строгое разделение: описание некоторых таблиц может оказаться невозможным без описания миграции, а enum'ы могут также требовать маппинга. Поэтому шаблоны гибкие и могут содержать разную информацию в зависимости от самого требования. Но прежде чем писать требования, нужен фундамент — документированная база данных.

Документирование базы данных в вики

Я работаю с PostgreSQL, поэтому примеры и структура заточены под реляционные базы данных.

Зачем вообще описывать базу данных в вики — разве физической структуры недостаточно? Самое важное тут, что именно описание в вики позволяет видеть не только структуру базы, но и логику данных — и писать точные требования. Физическая модель может быть перегружена таблицами связей и лишними данными. Её нельзя исключать, но не на ней фокус во время написания требований.

Чтобы документация по базе данных была наглядна и полна, я использую следующий подход: описываю базу данных в диаграмме, но в ней только основные таблицы, без таблиц связей и служебных, а из данных — только внешние и первичные ключи. Всё описание таблиц вместе с содержимым и бизнес-логикой хранится на отдельных страницах. То есть я описываю именно концептуальную схему, а если нужно во время написания требований указать таблицу связей — указываю её только там. Такие таблицы не имеют самостоятельного веса, они нужны только в контексте конкретных требований.

Для описания базы данных важна целая структура в вики, а не просто шаблон страницы.

> База данных
>> Схема (public)
>>> Таблица (order)
>>> Таблица (customer)
>> Схема (integrations)
>>> Таблица (sync_tasks)

Три уровня. На уровне схемы — просто группировка таблиц по PostgreSQL-схемам. Интереснее два других.

Уровень «База данных»

На корневой странице живёт концептуальная ER-диаграмма и общие правила. Структура:

Информация — назначение страницы. Ничего индивидуального придумывать не нужно, просто дать понять читателю, что перед ним.

ER-диаграмма — скриншот и ссылка на редактор (я использую dbdiagram.io). Под диаграммой — пояснение, что схема концептуальная и не содержит всех таблиц.

Архитектурные принципы — то, что относится ко всей базе:

  • Модель и нормальная форма

  • Стандарт именования

  • Общие данные по таблицам — то, что повторяется везде и не имеет смысла дублировать на каждой странице. Например, аудитные атрибуты. Вынос их на уровень базы данных — не мелочь. Если описывать на каждой странице таблицы, при изменении придётся обновлять десятки страниц. Здесь — обновил один раз.

Шаблон

# База данных — заголовок страницы
## Информация

**Назначение:**

## ER-диаграмма

[скриншот]
[ссылка на редактор]

## Архитектурные принципы

**Модель:**
**Именование:**

### Общие атрибуты таблиц

| Атрибут | Тип | Описание | Ограничения |
| --- | --- | --- | --- |
|  |  |  |  |
Пример заполнения

База данных

Информация

Назначение: На этой странице представлена общая концептуальная схема базы данных системы. Она показывает основные бизнес-сущности и их взаимосвязи на высоком уровне

ER-диаграмма

Untitled.png

dbdiagram.com

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

Архитектурные принципы

Модель: Реляционная, 1НФ

Именование: Используется нотация snake_case для всех объектов БД

Во многих таблицах есть аудитные атрибуты

Атрибут

Тип

Описание

Ограничения

created_at

timestamptz

Дата и время создания записи

created_by

varchar(255)

Идентификатор пользователя, создавшего запись

updated_at

timestamptz

Дата и время обновления записи

updated_by

varchar(255)

Идентификатор пользователя, обновившего запись

deleted_at

timestamptz

Дата и время удаления з��писи

deleted_by

varchar(255)

Идентификатор пользователя, удалившего запись

Уровень «Таблица»

Каждая бизнесовая таблица — отдельная страница. Структура:

Информация — назначение таблицы. Назначение — одно предложение о том, что хранит таблица.

Атрибуты — таблица с полями: название, тип, описание, ограничения. Первичные ключи помечаю 🔑, внешние — 🔐. Вычисляемые поля помечаю явно.

Ключи и связи — первичные, внешние и уникальные ключи отдельным блоком.

Аудитные поля — если стандартные, ссылаюсь на страницу БД. Если набор отличается — указываю явно.

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

Шаблон

# [название таблицы] — заголовок страницы
## Информация

**Назначение:**

## Атрибуты

| Атрибут | Тип | Описание | Ограничения |
| --- | --- | --- | --- |
|  |  |  |  |

## Ключи и связи

* **🔑 Первичный ключ:**
* **🔐 Внешние ключи:**
* **Уникальные ключи:**

## Аудитные поля

## Бизнес-правила
Пример заполнения

order

Информация

Назначение: хранение данных о заказах клиентов

Атрибуты

Атрибут

Тип

Описание

Ограничения

🔑 id

uuid

Идентификатор заказа

NOT NULL

🔐 customer_id

uuid

Идентификатор клиента

NOT NULL

status

varchar(50)

Статус заказа

NOT NULL, DEFAULT 'CREATED'

total_amount

numeric(21, 6)

(вычисляемое поле) Итоговая сумма заказа

NOT NULL

discount_amount

numeric(21, 6)

Сумма скидки

DEFAULT 0

final_amount

numeric(21, 6)

(вычисляемое) Итоговая сумма к оплате

NOT NULL

Ключи и связи

  • 🔑 Первичный ключ: id

  • 🔐 Внешние ключи: customer_idcustomer.id

  • Уникальные ключи: —

Аудитные поля

Стандартные (описаны на странице БД)

Бизнес-правила

  • final_amount = total_amount - discount_amount

  • Заказ не может быть удалён, если его статус DELIVERED

Требования к данным: таблицы и маппинг

Когда база данных описана, требования к данным становятся лаконичнее и понятнее. Не нужно дублировать всё содержимое таблицы, чтобы отразить её итоговое состояние: достаточно написать только те атрибуты, которые будут добавлены, и указать, какие будут удалены или изменены (и как).

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

ER-диаграмма — это не полное содержимое таблиц. Она должна показывать изменения. В ней отображаются все таблицы, которые затронуты в требовании, их связи, а содержимое таблиц — только те атрибуты, которые изменяются или добавляются.

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

Миграции — указываются при наличии правил переноса или заполнения информации в атрибутах таблицы.

Бизнес-правила — описываются новые правила, касающиеся таблицы. Если какое-то правило удаляется, оно тоже присутствует и обозначается как подлежащее удалению.

Маппинг — табличный вид сопоставления полей между внешним ресурсом и проектируемыми таблицами базы данных.

Если таблица новая — она описывается полностью и имеет такую же структуру, как описание таблицы в вики. Эта страница также создаётся в структуре описания базы данных — информация идёт туда. Зачем тогда дублировать? Требования к данным должны быть самодостаточными, чтобы разработчик не перескакивал со страницы на страницу и видел все изменения здесь и сразу. Ссылки на другие ресурсы нужны, но они должны дополнять информацию, а не быть обязательными для посещения.

Шаблон

# [ТД-N] Название требования — заголовок страницы
## ER-диаграмма

[скриншот]
[ссылка на редактор]

## Изменения в базе данных

### Таблица [название] — добавление полей

| Поле | Тип | Описание | Ограничения |
| --- | --- | --- | --- |
|  |  |  |  |

### Таблица [название] — изменение полей

| Поле | Изменение | Было | Стало |
| --- | --- | --- | --- |
|  |  |  |  |

**Миграция данных:**

**Бизнес-правила:**

### Таблица [название] — новая

**Назначение:**

**Атрибуты**

| Атрибут | Тип | Описание | Ограничения |
| --- | --- | --- | --- |
|  |  |  |  |

**Ключи и связи**

* **🔑 Первичный ключ:**
* **🔐 Внешние ключи:**
* **Уникальные ключи:**

**Аудитные поля**

**Бизнес-правила**

## Маппинг полей (Источник → БД)

| Поле источника | Поле БД | Комментарий |
| --- | --- | --- |
|  |  |  |

## Таблицы

* [список затронутых таблиц со ссылками]
Как работает на практике

[ТД-4] Внедрение программы лояльности

ER-диаграмма

dbdiagram.io

Изменения в базе данных

Таблица order — добавление полей

Поле

Тип

Описание

Ограничения

points_earned

integer

Количество баллов, начисленных за заказ

NOT NULL, DEFAULT 0

points_used

integer

Количество баллов, списанных при оплате

NOT NULL, DEFAULT 0

Миграция данных:

  • Для всех существующих заказов со статусом DELIVERED установить points_earned = floor(final_amount / 100)

  • Для всех существующих заказов установить points_used = 0

Бизнес-правила:

  • points_earned заполняется при смене статуса заказа на DELIVERED

  • points_used не может превышать доступный баланс баллов клиента

  • points_used не может превышать final_amount заказа (в пересчёте 1 балл = 1 рубль)

Таблица loyalty_points — новая

Назначение: хранение транзакций по бонусным баллам клиентов

Атрибуты

Атрибут

Тип

Описание

Ограничения

🔑 id

uuid

Идентификатор транзакции

NOT NULL

🔐 customer_id

uuid

Идентификатор клиента

NOT NULL

🔐 order_id

uuid

Идентифик��тор заказа

NOT NULL

type

varchar(20)

Тип транзакции: EARN или SPEND

NOT NULL

amount

integer

Количество баллов

NOT NULL, CHECK (>0)

balance

integer

(вычисляемое поле) Баланс баллов клиента после транзакции

NOT NULL

Ключи и связи

  • 🔑 Первичный ключ: id

  • 🔐 Внешние ключи: customer_idcustomer.id, order_idorder.id

  • Уникальные ключи: —

Аудитные поля

Стандартные (описаны на странице БД)

Бизнес-правила

  • balance = сумма amount где type = 'EARN' минус сумма amount где type = 'SPEND' для данного customer_id

  • balance не может быть отрицательным

Маппинг полей (Loyalty API → БД)

loyalty_points

Поле API

Поле БД

Комментарий

transactionId

id

userId

customer_id

Маппинг: найти customer.id по external_id

orderId

order_id

Маппинг: найти order.id по external_order_id

transactionType

type

ACCRUALEARN, REDEMPTIONSPEND

points

amount

balance

Вычисляется в БД

order

Поле API

Поле БД

Комментарий

earnedPoints

points_earned

redeemedPoints

points_used

Таблицы

Требования к данным: миграции

Есть задачи, в которых нужно изменять не сами таблицы, а существующую в них информацию. Например, при появлении нового бизнес-правила старые данные могут оказаться в неконсистентном состоянии и их нужно привести в порядок. Полноценная страница с ER-диаграммой и описанием таблиц здесь не требуется. Достаточно описать суть изменений.

# [ТД-N] Название миграции — заголовок страницы
## Изменения в базе данных

**Описание:**

**Миграция данных:**

**Таблицы**
Как работает на практике

[ТД-5] Миграция данных: начисление бонусных баллов по существующим заказам

Изменения в базе данных

Описание: После внедрения программы лояльности система начисляет баллы за заказы автоматически. Существующие заказы со статусом DELIVERED не имеют начисленных баллов. Необходимо привести данные в консистентное состояние.

Миграция данных:

  • Для всех заказов со статусом DELIVERED и points_earned = 0: установить points_earned = floor(final_amount / 100)

  • Для каждого такого заказа создать запись в loyalty_points с type = 'EARN', amount = points_earned

  • Пересчитать balance в loyalty_points для каждого клиента

  • Используется soft-подход: created_by не заполняется — системная миграция (NULL)

Таблицы

Требования к данным: enum’ы

Данные существуют не только в рамках БД, но и в коде. Чаще всего я встречался с enum'ами, другие виды данных в коде — редко. Поэтому заострю внимание именно на них.

Описание требований к enum'ам чем-то похоже на описание изменений в таблице БД, но короче:

  • Название enum

  • Назначение — для чего создаётся/существует enum и как от него зависят другие сущности: например, используется как query-параметр или нужен для валидации

  • Значения — записываются в табличном виде. Заголовки таблицы определяют атрибуты enum, поэтому этот раздел убивает сразу двух зайцев

  • Миграции (если есть)

  • Бизнес-правила (если есть)

  • Примечание (если есть) — для информации, которая не помещается в остальные пункты шаблона. Чаще отсутствует, но нужно его учитывать

Отдельная тема — enum'ы статусов, которые по сути описывают конечный автомат с переходами между состояниями. Это заслуживает отдельного разговора.

# [ТД-N] Название требования — заголовок страницы
## Изменения в коде

## [Название enum]

**Назначение:**

### Значения

| Значение | ... | ... |
| --- | --- | --- |
|  |  |  |

### Миграции

### Бизнес-правила

### Примечание
Как работает на практике

[ТД-6] Создание enum'а для типов транзакций лояльности

Изменения в коде

PointsTransactionType

Назначение:

  • Определение типов транзакций в программе лояльности

  • Enum используется для валидации поля type в таблице loyalty_points

  • Используется как query-параметр в GET /loyalty/transactions для фильтрации по типу

Значения

Значение

displayName

Описание

EARN

Начисление

Начисление баллов за завершённый заказ

SPEND

Списание

Списание баллов при оплате заказа

EXPIRE

Сгорание

Автоматическое сгорание баллов по истечении срока

Бизнес-правила

  • При создании транзакции с типом SPEND проверяется, что баланс клиента не станет отрицательным

  • Транзакции с типом EXPIRE создаются автоматически по расписанию

Примечание

Enum OrderStatus остаётся без изменений — новых статусов для программы лояльности не требуется

Итог

Работа с требованиями к данным — комплексная задача. Хорошо описанная база данных в вики помогает сэкономить много времени при написании требований. Как вы описываете изменения в базе данных — в отдельных документах или прямо в задачах для разработки?