ADR-0007: Database Schema Design¶
Статус¶
Accepted
Контекст¶
Финансовая система требует надёжной, нормализованной схемы базы данных, которая: - Обеспечивает ACID свойства для финансовых операций - Поддерживает аудит всех изменений состояния - Разделяет пользователей и их кошельки (многие ко многим) - Предотвращает double-spending и race conditions - Обеспечивает быструю выборку транзакций (балансы рассчитываются динамически) - Поддерживает различные типы операций (deposits, withdrawals, investments)
Решение¶
Нормализованная схема PostgreSQL с чётким разделением responsibility и строгими constraint.
Ключевые таблицы:
-
users - Основная таблица пользователей
CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, status VARCHAR(50) NOT NULL DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- НЕТ поля wallet_address! Связь через отдельную таблицу -
wallets - Унифицированная таблица кошельков (заменяет user_wallet_addresses)
CREATE TABLE wallets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, address VARCHAR(42) NOT NULL UNIQUE, network_id INTEGER NOT NULL DEFAULT 1337, -- Saga VPS production по умолчанию type VARCHAR(20) NOT NULL DEFAULT 'deposit', -- deposit, personal, master, hot, cold, trading status VARCHAR(20) NOT NULL DEFAULT 'active', -- HD Wallet fields derivation_path VARCHAR(255), -- HD wallet derivation path (m/44'/60'/0'/0/index) address_index INTEGER, -- HD wallet derivation index is_hd_generated BOOLEAN NOT NULL DEFAULT false, is_primary BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); -
transactions - ВСЕ финансовые операции
CREATE TABLE transactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, type VARCHAR(20) NOT NULL, -- 'deposit', 'withdrawal', 'investment' amount DECIMAL(20,8) NOT NULL CHECK (amount > 0), currency VARCHAR(10) NOT NULL DEFAULT 'USDC', status VARCHAR(20) NOT NULL DEFAULT 'pending', blockchain_tx_hash VARCHAR(66), from_address VARCHAR(42), to_address VARCHAR(42), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -
investments - Инвестиционные позиции
CREATE TABLE investments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, strategy_name VARCHAR(100) NOT NULL, amount DECIMAL(20,8) NOT NULL CHECK (amount > 0), currency VARCHAR(10) NOT NULL DEFAULT 'USDC', status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
Альтернативы¶
Денормализованная схема с wallet_address в users¶
Отклонена - не поддерживает множественные кошельки на пользователя, усложняет HD Wallet архитектуру.
NoSQL база данных (MongoDB, DynamoDB)¶
Отклонена - отсутствие ACID транзакций, сложность поддержания консистентности финансовых данных.
Единая таблица для всех операций (Event Sourcing)¶
Рассматривалась - избыточная сложность для текущих задач, сложность queries для балансов.
Отдельные таблицы для каждого типа транзакций¶
Отклонена - фрагментация, сложность агрегации, дублирование полей.
Последствия¶
Положительные¶
- ACID свойства для всех финансовых операций
- Чёткое разделение данных по назначению
- Поддержка множественных кошельков на пользователя
- Простота добавления новых типов операций
- Эффективные индексы для быстрых queries
- Полная audit trail всех операций
Отрицательные¶
- Необходимость JOIN операций для получения связанных данных
- Дополнительная сложность схемы по сравнению с денормализованной
- Необходимость поддержания referential integrity
Нейтральные¶
- Использование UUID вместо auto-increment ID (лучше для distributed systems)
- Необходимость осторожного проектирования миграций
Связанные решения¶
- Связано с: ADR-0001 (Web3 Wallet Authentication) - для связи users ↔ wallet_addresses
- Связано с: ADR-0004 (HD Wallet Architecture) - для генерации deposit addresses
- Связано с: ADR-0003 (No-Mock Testing Architecture) - для тестирования с реальной БД
Примечания¶
Принципы проектирования:
- Нормализация: Каждая сущность в своей таблице
- Constraint: Строгие проверки на уровне БД
- Audit: Поля created_at/updated_at везде
- Consistency: DECIMAL для денежных значений (никогда FLOAT)
- Security: UUID как primary keys (не последовательные)
Важные constraint:
- Все денежные amount > 0
- Все балансы >= 0
- Уникальность wallet_address
- Один deposit адрес на пользователя
- Cascade delete для сохранения integrity
Индексы для производительности:
-- Быстрый поиск по wallet адресу
CREATE INDEX idx_wallets_address ON wallets(address);
CREATE INDEX idx_wallets_user_id ON wallets(user_id);
CREATE INDEX idx_wallets_network_id ON wallets(network_id);
-- HD Wallet специфичные индексы
CREATE INDEX idx_wallets_hd_network_id_index ON wallets(network_id, address_index) WHERE is_hd_generated = true;
-- Быстрый поиск транзакций пользователя
CREATE INDEX idx_transactions_user_id ON transactions(user_id);
CREATE INDEX idx_transactions_status ON transactions(status);
-- Быстрый поиск инвестиций
CREATE INDEX idx_investments_user_id ON investments(user_id);
CREATE INDEX idx_investments_status ON investments(status);
Типы данных:
- UUID - для всех ID (безопасность, уникальность)
- DECIMAL(20,8) - для денежных сумм (точность без floating point ошибок)
- VARCHAR с ограничениями - для строк с валидацией
- TIMESTAMP - для всех временных меток
Миграции:
- Все изменения схемы только через миграции
- Никаких прямых изменений в psql
- Backward compatible миграции где возможно
- Тестирование миграций на копии production данных
Безопасность:
- Никаких админских таблиц (адреса в конфигурации)
- Row-level security через user_id
- Регулярные backup процедуры
- Валидация данных на уровне БД и приложения
Связи между таблицами:
users 1:N wallets (заменяет user_wallet_addresses)
users 1:N transactions (единый источник истины для балансов)
users 1:N investments
Критическое правило: НИКОГДА не изменять схему вручную. Всегда создавать миграции для любых изменений структуры БД.