реклама
Бургер менюБургер меню

Юрий Белк – Full stack Developer (страница 34)

18

– Audit log

– Idempotency keys (для безопасного повтора POST)

Сразу полезное правило: таблица – это не “модель в коде”, а “факт в мире”.

Например, “участник workspace” – это факт, значит это отдельная таблица membership.

9.3. Нормализация: что это вообще и зачем вам это сегодня

Нормализация – это не ритуал. Это способ уменьшить вероятность того, что:

– вы храните одно и то же в пяти местах,

– оно расходится,

– и потом вы неделями чините отчёты и поиск.

9.3.1. Практическое правило №1: “Один факт – одно место”

Если роль пользователя в workspace хранится и в users, и в workspace_members, и в каком-то JSON – однажды вы поймаете ситуацию: “в одном месте admin, в другом member”. И вы проиграете спор с реальностью.

Роль в рамках workspace – это факт membership. Значит:

– workspace_members.role – единственный источник.

9.3.2. Практическое правило №2: “Массивы и списки – осторожно”

Например, хочется хранить метки как массив строк прямо в tasks.labels.

Это удобно первые два дня. Потом вы захотите:

– переименовать метку,

– вывести список всех меток workspace,

– посчитать статистику по меткам,

– не допускать дубликаты,

– индексировать фильтр по метке.

И внезапно вы понимаете, что метка – сущность, и у неё должна быть таблица.

Вывод: метки – отдельная таблица labels, а связь – task_labels.

9.3.3. Практическое правило №3: “Денормализация допустима, но осознанно”

Мы можем хранить workspace_id в tasks, даже если его можно вычислить через project. Это “снятие лишнего JOIN” для производительности и удобства фильтров.

Но тогда нужен механизм, который гарантирует консистентность (например, constraint, триггер, или правило в коде + тесты). В учебной системе можно держать workspace_id в tasks и контролировать его в приложении, а в идеале – усилить constraint’ами.

9.4. Схема данных: таблицы и ключевые поля

Ниже – списками, без таблиц (как вы просили). Названия – в стиле snake_case.

9.4.1. users

– id (uuid или bigserial; для современных систем uuid очень удобен)

– email (unique, not null)

– password_hash (not null)

– name (nullable)

– created_at (not null, default now())

– updated_at (not null, default now())

– deleted_at (nullable) – если хотите soft delete для пользователей (часто не надо; чаще “deactivated”)

Индексы:

– уникальный индекс на email

Constraints:

– email не пустой (можно простое CHECK (email <> ''))

– лучше хранить email в lower-case или нормализовать на входе

9.4.2. workspaces

– id

– name (not null)

– owner_user_id (not null, FK → users.id)

– created_at, updated_at

– deleted_at (nullable, если soft delete workspace)

Индексы:

– индекс на owner_user_id

Constraints:

– FK на owner

– CHECK (name <> '')

9.4.3. workspace_members

Это центральная таблица для прав.

– workspace_id (FK → workspaces.id)

– user_id (FK → users.id)

– role (not null; например: owner/admin/member)

– created_at

Ключи:

– составной PK (workspace_id, user_id) – так проще запретить дубль участия.

Constraints:

– role ограничить через CHECK (role IN ('owner','admin','member'))

или через PostgreSQL ENUM (ENUM удобен, но миграции могут быть чуть менее приятными; CHECK проще менять).

Индексы:

– индекс на user_id (быстрый поиск “где состоит пользователь”)