0001. Выбор ORM и системы миграций
Копия ADR из репозитория:
backend/docs/adr/0001-orm-and-migrations.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-05-29
- Связанные задачи: OP-37 (F4 · Платформа данных)
Контекст и проблема
Бэкенд «Оптима» на NestJS + PostgreSQL (см. план проекта). Нужен ORM/инструмент доступа к данным и управляемые миграции для всех модулей. В ТЗ (раздел 9.3) детали БД помечены «уточняется». Решение влияет на все модульные эпики (OP-9..OP-23) и блокирует первый «настоящий» модульный тикет, поэтому фиксируется до начала их работы.
Рассмотренные варианты
- TypeORM — нативная интеграция с NestJS (
@nestjs/typeorm), декораторные сущности, миграции из коробки (typeorm migration:generate|run|revert), родная поддержка JSONB и кастомных типов черезColumnType. Подходит под модульный монолит: репозитории и сущности живут рядом с модулем. Минус: исторически спорная поддержка сложных аналитических запросов — снимается тем, что тяжёлые отчёты в OP-40 (Spike) могут идти через raw SQL/QueryBuilder. - Prisma — типобезопасный клиент, удобные миграции (
prisma migrate), хорошая DX. Минус: отдельная схема.prismaвне TS-исходников, менее «декораторный» стиль, в Nest требует своего модуля-обёртки, и интеграция сclass-validatorдля входных DTO остаётся ручной. - MikroORM — Unit of Work/Identity Map, хорошая типизация, есть Nest-модуль. Минус: менее распространён, меньше примеров под NestJS-стек проекта и под профиль команды (solo + AI).
Решение
Выбран TypeORM + @nestjs/typeorm. Миграции — встроенный механизм TypeORM
(migration:generate|run|revert|show), CLI запускается через data-source.ts
из backend/src/core/database/.
Причины:
- Декораторные сущности и репозитории живут в TS-исходниках модуля — это совпадает с модульным монолитом (см. ADR-0002).
- Нативная интеграция с DI Nest:
@InjectRepository(Entity)без обёрток. - JSONB поддерживается типом
'jsonb'из коробки — критично для полу-структурированных данных WB (заказы, отчёты). - Для тяжёлых аналитических запросов (OP-40) допустимо использовать
QueryBuilderили raw SQL через тот жеDataSource— это не блокирующее ограничение.
Последствия
- Положительные:
- Единый слой доступа к данным; модули OP-9..OP-23 пишут репозитории однотипно.
- Миграции — в репозитории, применение в CI (OP-52) и dev предсказуемо.
- Базовая сущность с
id (uuid),created_at,updated_at,deleted_at(soft-delete) — переиспользуется во всех модулях.
- Отрицательные / компромиссы:
- Смена ORM позже — дорого; решение блокирует OP-38 и далее.
- Для самых тяжёлых отчётов придётся опускаться в
QueryBuilder/ raw SQL — допустимо как точечное исключение.
- Дальше:
- Убрать прямой
pg.PoolвDatabaseModule, заменить наTypeOrmModule.forRootAsync(OP-37). - Зафиксировать конвенции схемы и миграций в
backend/docs/database.md(OP-37). - Подсветить выбор в Layer-1 доках при необходимости (только если касается наблюдаемого поведения).
- Убрать прямой