Другое

Как организовать связи в Prisma ORM для лучшей читаемости

Практическое руководство по структурированию сложных моделей Prisma ORM с группировкой полей, разделением схемы на модули и лучшими практиками поддержки.

Как эффективно организовать множество связей в модели Prisma ORM, чтобы не потеряться в структуре данных?

У меня есть модель UserAccount с множеством связей и полей:

prisma
model UserAccount {
  id Int @id @unique @default(autoincrement()) @map("id")
  roles Role[] @default([USER]) @map("roles")
  invitedBy Int? @map("invited_by")
  invitationLinks String[] @map("invitation_links")
  actions UserAction[]
  bookings Booking[]
  notifications Notification[]
  createdProducts Product[] @relation("user_created_products")
  isBlocked Boolean @map("is_blocked")
  blockedBy Int @map("blocked_by")
  blockedAt DateTime @map("blocked_at") @db.Timestamptz()
  blockingReason String @map("blocking_reason") @db.VarChar(128)
  isDeleted Boolean @default(false) @map("is_deleted")
  deletedBy Int @map("deleted_by")
  deletedAt DateTime? @map("deleted_at") @db.Timestamptz()
  deletionReason String? @map("deletion_reason") @db.VarChar(128)
  createdBy Int? @map("created_by")
  createdAt DateTime @map("created_at") @db.Timestamptz()
  updatedBy Int? @map("updated_by")
  updatedAt DateTime? @map("updated_at") @db.Timestamptz()
  tablesAuditLogs TableAuditLog[]
  @@map("user_accounts")
}

Мои основные проблемы:

  1. При увеличении количества связей становится сложно ориентироваться в модели
  2. Не понимаю, как группировать поля - например, отделить технические связи от основных (таких как bookings)
  3. Хотелось бы разделить схемы на интуитивно понятные блоки для удобства разработки и поддержки

Как структурировать сложные модели Prisma ORM для лучшей читаемости и поддержки? Какие существуют лучшие практики организации связей и полей в больших проектах?

Эффективная организация множества связей в Prisma ORM достигается через группировку полей по функциональным блокам, разделение схемы на логические модули и применение четких соглашений по именованию. Для вашей модели UserAccount рекомендуется разделить поля на основные связи (bookings, notifications, products), технические поля (audit logs, soft delete) и системные поля (timestamps, createdBy), а также вынести связанные модели в отдельные файлы по доменным областям.

Содержание


Основные принципы организации сложных моделей

При работе с большими моделями Prisma, содержащими множество связей, важно придерживаться следующих принципов:

Декларативный подход к моделированию данных
Prisma следует философии “Minimalist, declarative, and data-model first”, где схема базы данных является источником правды. Это означает, что ваша схема должна быть максимально понятной и отражать бизнес-логику приложения.

Логическая группировка полей
Все поля в модели следует группировать по их назначению и функциональности. Для вашей модели UserAccount можно выделить следующие группы:

  1. Основные бизнес-сущности - поля, представляющие основную сущность пользователя
  2. Связи с другими сущностями - внешние связи типа one-to-many, many-to-many
  3. Системные технические поля - поля для аудита, мягкого удаления, блокировок
  4. Метаданные и временные метки - createdAt, updatedAt и подобные

Четкая иерархия связей
Каждая связь должна иметь понятное имя и соответствовать бизнес-контексту приложения. Избегайте абстрактных названий вроде relation1, relation2.


Группировка полей и связей

Разделение по функциональности

Для вашей модели UserAccount рекомендуется следующая группировка:

prisma
// Основные поля пользователя
model UserAccount {
  id        Int @id @unique @default(autoincrement())
  roles     Role[] @default([USER])
  
  // Основные бизнес-связи
  bookings      Booking[]
  notifications Notification[]
  products      Product[] @relation("user_created_products")
  
  // Технические связи и аудит
  invitedBy           Int?
  invitationLinks     String[]
  actions             UserAction[]
  tablesAuditLogs     TableAuditLog[]
  
  // Системные поля управления
  isBlocked     Boolean @default(false)
  blockedBy     Int
  blockedAt     DateTime @db.Timestamptz()
  blockingReason String @db.VarChar(128)
  
  // Мягкое удаление
  isDeleted     Boolean @default(false)
  deletedBy     Int?
  deletedAt     DateTime? @db.Timestamptz()
  deletionReason String? @db.VarChar(128)
  
  // Стандартные поля аудита
  createdBy Int?
  createdAt DateTime @db.Timestamptz()
  updatedBy Int?
  updatedAt DateTime? @db.Timestamptz()
  
  @@map("user_accounts")
}

Комментарии и документация

Добавление комментариев помогает лучше понимать назначение полей:

prisma
model UserAccount {
  // Основные идентификаторы
  id        Int @id @unique @default(autoincrement()) @map("id")
  
  // Роли и разрешения пользователя
  roles     Role[] @default([USER]) @map("roles")
  
  // Приглашения и рефералы
  invitedBy Int? @map("invited_by")
  invitationLinks String[] @map("invitation_links")
  
  // Основные бизнес-сущности
  bookings      Booking[] // Бронирования пользователя
  notifications Notification[] // Уведомления пользователя
  products      Product[] @relation("user_created_products") // Созданные продукты
  
  // Действия пользователя
  actions UserAction[] // История действий пользователя
  
  // Аудит и логирование
  tablesAuditLogs TableAuditLog[] // Логи аудита таблиц
  
  // Блокировка аккаунта
  isBlocked     Boolean @default(false) @map("is_blocked")
  blockedBy     Int @map("blocked_by")
  blockedAt     DateTime @map("blocked_at") @db.Timestamptz()
  blockingReason String @map("blocking_reason") @db.VarChar(128)
  
  // Мягкое удаление
  isDeleted     Boolean @default(false) @map("is_deleted")
  deletedBy     Int? @map("deleted_by")
  deletedAt     DateTime? @map("deleted_at") @db.Timestamptz()
  deletionReason String? @map("deletion_reason") @db.VarChar(128)
  
  // Стандартные поля аудита
  createdBy Int? @map("created_by")
  createdAt DateTime @map("created_at") @db.Timestamptz()
  updatedBy Int? @map("updated_by")
  updatedAt DateTime? @map("updated_at") @db.Timestamptz()
  
  @@map("user_accounts")
}

Разделение схемы на файлы и модули

Структурное разделение по доменным областям

Для больших проектов рекомендуется разделять схему Prisma на несколько файлов по доменным областям:

prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// Базовые модели и типы
include "./base/models.prisma"
include "./base/enums.prisma"

// Модели пользователей
include "./user/models.prisma"
include "./user/enums.prisma"

// Модели контента
include "./content/models.prisma"

// Модели бизнес-логики
include "./business/models.prisma"

Модели пользователей (user/models.prisma)

prisma
// prisma/user/models.prisma
model UserAccount {
  // Основные поля
  id        Int @id @unique @default(autoincrement())
  email     String @unique @db.VarChar(255)
  name      String? @db.VarChar(255)
  
  // Роли и разрешения
  roles     Role[] @default([USER])
  
  // Бизнес-связи
  bookings      Booking[]
  notifications Notification[]
  products      Product[] @relation("user_created_products")
  
  // Приглашения
  invitedBy Int?
  invitationLinks String[]
  
  @@map("user_accounts")
}

enum Role {
  USER
  ADMIN
  MODERATOR
}

Модели аудита и системные (base/models.prisma)

prisma
// prisma/base/models.prisma
model AuditLog {
  id        Int @id @default(autoincrement())
  action    String @db.VarChar(64)
  entityId  Int
  entityType String @db.VarChar(64)
  changes   Json?
  userId    Int?
  createdAt DateTime @default(now()) @db.Timestamptz()
  
  @@map("audit_logs")
}

model Timestamped {
  createdAt DateTime @default(now()) @db.Timestamptz()
  updatedAt DateTime @updatedAt @db.Timestamptz()
}

Расширенные модели с наследованием

Для уменьшения дублирования можно использовать подход с базовыми моделями:

prisma
model SoftDeletable {
  isDeleted     Boolean @default(false)
  deletedBy     Int?
  deletedAt     DateTime? @db.Timestamptz()
  deletionReason String? @db.VarChar(128)
}

model Auditable {
  createdBy Int?
  createdAt DateTime @default(now()) @db.Timestamptz()
  updatedBy Int?
  updatedAt DateTime @updatedAt @db.Timestamptz()
}

model UserAccount {
  // Наследуем базовые поля
  @@extends SoftDeletable, Auditable
  
  // Основные поля
  id        Int @id @unique @default(autoincrement())
  email     String @unique @db.VarChar(255)
  name      String? @db.VarChar(255)
  
  // Остальные поля...
}

Практические примеры организации

Пример 1: E-commerce платформа

prisma
// prisma/ecommerce/models.prisma
model Customer {
  // Основная информация
  id        Int @id @default(autoincrement())
  email     String @unique
  phone     String?
  firstName String
  lastName  String
  
  // Связи с заказами
  orders         Order[]
  orderItems     OrderItem[]
  reviews        Review[]
  wishlists      Wishlist[]
  
  // Адреса доставки
  addresses      DeliveryAddress[]
  
  // Платежная информация
  paymentMethods PaymentMethod[]
  
  // Лояльность
  loyaltyPoints  Int @default(0)
  membershipTier MembershipTier @default(BRONZE)
  
  // Системные поля
  @@extends SoftDeletable, Auditable
  @@map("customers")
}

model Order {
  id        Int @id @default(autoincrement())
  orderNo   String @unique
  status    OrderStatus @default(PENDING)
  total     Decimal @db.Decimal(10, 2)
  
  // Связи
  customer Customer @relation(fields: [customerId], references: [id])
  items    OrderItem[]
  payments Payment[]
  
  // Доставка
  shippingAddress DeliveryAddress @relation(fields: [shippingAddressId], references: [id])
  shippingAddressId Int
  
  // Системные поля
  @@extends SoftDeletable, Auditable
  @@map("orders")
}

Пример 2: SaaS платформа с командами

prisma
// prisma/saas/models.prisma
model Team {
  id          Int @id @default(autoincrement())
  name        String
  description String?
  
  // Связи
  members     TeamMember[]
  projects    Project[]
  workspaces  Workspace[]
  
  // Настройки
  settings    Json?
  
  // Системные поля
  @@extends SoftDeletable, Auditable
  @@map("teams")
}

model TeamMember {
  id     Int @id @default(autoincrement())
  teamId Int
  userId Int
  
  // Роль в команде
  role   TeamRole @default(MEMBER)
  
  // Дополнительные данные
  joinedAt DateTime @default(now())
  
  // Связи
  team Team @relation(fields: [teamId], references: [id])
  user User @relation(fields: [userId], references: [id])
  
  @@unique([teamId, userId])
  @@map("team_members")
}

Инструменты и практики для поддержки

Визуализация схемы

Используйте инструменты для визуализации вашей схемы Prisma:

bash
# Генерация ERD диаграммы
npx prisma schema visualize

Автоматическая генерация документации

Создайте автоматическую документацию вашей схемы:

prisma
// В schema.prisma добавьте
generator docs {
  provider = "prisma-docs-generator"
  output   = "./docs"
}

Линтеры для Prisma

Используйте линтеры для поддержания качества кода:

json
// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "printWidth": 100
}

Организация миграций

Структурируйте миграции по версиям и функциональности:

bash
# Структура папок миграций
prisma/migrations/
├── 20250101_000001_initial_schema/
├── 20250102_000002_add_user_roles/
├── 20250103_000003_add_soft_delete/
└── 20250104_000004_add_audit_logs/

Мониторинг производительности

Используйте инструменты для мониторинга производительности запросов:

typescript
// middleware/prisma.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient({
  log: [
    {
      emit: 'event',
      level: 'query',
    },
    {
      emit: 'event',
      level: 'error',
    },
    {
      emit: 'stdout',
      level: 'info',
    },
    {
      emit: 'stdout',
      level: 'warn',
    },
  ],
})

prisma.$on('query', (e) => {
  console.log('Query: ' + e.query)
  console.log('Params: ' + e.params)
  console.log('Duration: ' + e.duration + 'ms')
})

export { prisma }

Заключение

При работе со сложными моделями Prisma ORM важно придерживаться следующих ключевых практик:

  1. Логическая группировка полей - разделяйте поля на бизнес-сущности, технические связи и системные метаданные для лучшей читаемости

  2. Модульная организация схемы - выносите связанные модели в отдельные файлы по доменным областям, чтобы избежать перегруженного основного файла схемы

  3. Стандартизация именования - используйте единые соглашения для имен связей, полей и моделей в рамках проекта

  4. Документирование схемы - добавляйте комментарии к сложным полям и связям, объясняющие их бизнес-назначение

  5. Визуализация и мониторинг - используйте инструменты для визуализации схемы и мониторинга производительности запросов

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

Источники

  1. Prisma ORM Production Guide: Next.js Complete Setup 2025 - Руководство по производственной настройке Prisma ORM с примерами well-designed схем

  2. The Complete NestJS & Prisma Backend Masterclass - Информация о работе с complex database relations в Prisma

  3. The Art of the Map: Navigating the Modern ORM Landscape - Философия Prisma: “Minimalist, declarative, and data-model first”

  4. Using Prisma for Schema and Kysely for Queries in a Next.js App - Примеры организации схем Prisma в реальных проектах

  5. 5 Best Database Schema Design Examples in 2025 - Лучшие практики проектирования схем баз данных

Авторы
Проверено модерацией
Модерация