Files
pos-system/docs/vi/skills/database-prisma.md
Ho Ngoc Hai 2640b351c3 Enhance documentation with detailed diagrams and structured flows
- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes.
- Introduced configuration loading flow in configuration-management skill to clarify the configuration process.
- Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers.
- Enhanced various skills with additional diagrams to improve understanding of complex concepts.

These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
2026-01-01 23:22:54 +07:00

23 KiB

Cơ Sở Dữ Liệu & Prisma

Prisma ORM and database patterns for GoodGo microservices. Use when working with databases, creating Prisma schemas, writing migrations, implementing repositories, or optimizing queries.

Pattern Prisma ORM và database cho các microservices của GoodGo. Sử dụng khi làm việc với databases, tạo Prisma schemas, viết migrations, triển khai repositories, hoặc tối ưu queries.

Tổng Quan

This skill covers Prisma ORM patterns, database schema design, repository patterns, query optimization, and transaction handling used across GoodGo microservices. It ensures type-safe database operations, consistent data access patterns, and optimal performance.

Skill này bao gồm các pattern Prisma ORM, thiết kế database schema, repository patterns, tối ưu queries, và xử lý transactions được sử dụng trong các microservices của GoodGo. Nó đảm bảo các thao tác database type-safe, pattern truy cập dữ liệu nhất quán, và hiệu năng tối ưu.

Khi Nào Sử Dụng

Use this skill when:

  • Setting up Prisma for a new service
  • Creating or modifying database schemas
  • Writing database migrations
  • Implementing repository patterns
  • Optimizing database queries
  • Setting up database connections
  • Implementing transactions
  • Working with Neon PostgreSQL

Sử dụng skill này khi:

  • Thiết lập Prisma cho service mới
  • Tạo hoặc sửa đổi database schemas
  • Viết database migrations
  • Triển khai repository patterns
  • Tối ưu database queries
  • Thiết lập kết nối database
  • Triển khai transactions
  • Làm việc với Neon PostgreSQL

Khái Niệm Chính

Pattern Repository

All database operations go through repository classes that extend BaseRepository, providing consistent CRUD operations and error handling.

Tất cả các thao tác database đi qua các repository classes kế thừa BaseRepository, cung cấp các CRUD operations nhất quán và xử lý lỗi.

Schema Prisma

Database schema is defined in prisma/schema.prisma with models, relations, indexes, and constraints.

Database schema được định nghĩa trong prisma/schema.prisma với models, relations, indexes, và constraints.

An Toàn Kiểu

Prisma generates TypeScript types from schema, ensuring type-safe database operations.

Prisma generate TypeScript types từ schema, đảm bảo các thao tác database type-safe.

Các Pattern Thường Dùng

Pattern Schema Prisma

Example from codebase / Ví dụ từ codebase:

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

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

User model - Core user entity
// VI: Model User - Entity người dùng cốt lõi
model User {
  id            String    @id @default(cuid())
  email         String    @unique
  username      String?   @unique
  passwordHash  String?   // Nullable for social-only users
  isActive      Boolean   @default(true)
  emailVerified Boolean   @default(false)
  mfaEnabled    Boolean   @default(false)
  mfaSecret     String?
  lastLoginAt   DateTime?
  loginCount    Int       @default(0)
  organizationId String?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  
  // Relations
  userRoles     UserRole[]
  userPermissions UserPermission[]
  sessions      Session[]
  organization   Organization? @relation(fields: [organizationId], references: [id])
  profile        UserProfile?
  
  @@index([email])
  @@index([username])
  @@index([createdAt])
  @@index([organizationId])
  @@map("users")
}

Sơ Đồ Quan Hệ Schema / Schema Relationships Diagram

Sơ đồ sau minh họa các quan hệ giữa các models User, Post, và Profile:

The following diagram illustrates the relationships between User, Post, and Profile models:

erDiagram
    User ||--o{ Post : "has many"
    User ||--o| Profile : "has one"
    
    User {
        string id PK
        string email UK
        string name
        string password
        enum role
        datetime createdAt
        datetime updatedAt
    }
    
    Post {
        string id PK
        string title
        string content
        boolean published
        string authorId FK
        datetime createdAt
        datetime updatedAt
    }
    
    Profile {
        string id PK
        string bio
        string avatar
        string userId FK,UK
    }

Pattern Kết Nối Database

Example from codebase / Ví dụ từ codebase:

// services/iam-service/src/config/database.config.ts
import { PrismaClient } from '@prisma/client';
import { logger } from '@goodgo/logger';

/**
Prisma client instance configured for the application
 * VI: Instance Prisma client được cấu hình cho ứng dụng
 */
export const prisma = new PrismaClient({
Enable detailed logging in development, minimal in production
  // VI: Bật ghi log chi tiết trong development, tối thiểu trong production
['error'],
});

/**
Establish database connection on application startup
 * VI: Thiết lập kết nối database khi khởi động ứng dụng
 */
export const connectDatabase = async (): Promise<void> => {
  try {
    await prisma.$connect();
    logger.info('Database connected successfully / Kết nối database thành công');
  } catch (error) {
    logger.error('Database connection failed / Kết nối database thất bại', { error });
    process.exit(1);
  }
};

/**
Close database connection on application shutdown
 * VI: Đóng kết nối database khi tắt ứng dụng
 */
export const disconnectDatabase = async (): Promise<void> => {
  await prisma.$disconnect();
  logger.info('Database disconnected / Đã ngắt kết nối database');
};

Pattern Base Repository

Example from codebase / Ví dụ từ codebase:

// services/iam-service/src/modules/common/repository.ts
import { PrismaClient } from '@prisma/client';
import { logger } from '@goodgo/logger';
import { DatabaseError } from '../../errors/http-error';

/**
Base repository class providing common database operations
 * VI: Base repository class cung cấp các thao tác database chung
 */
export abstract class BaseRepository<T, CreateInput, UpdateInput> {
  protected prisma: PrismaClient;
  protected modelName: string;

  constructor(prisma: PrismaClient, modelName: string) {
    this.prisma = prisma;
    this.modelName = modelName;
  }

  /**
Find entity by ID
   * VI: Tìm entity theo ID
   */
  async findById(id: string): Promise<T | null> {
    try {
      logger.debug(`Finding ${this.modelName} by ID / Tìm ${this.modelName} theo ID`, { id });

      const entity = await (this.prisma as any)[this.modelName].findUnique({
        where: { id },
      });

      logger.debug(`${this.modelName} ${entity ? 'found' : 'not found'} / ${this.modelName} ${entity ? 'đã tìm thấy' : 'không tìm thấy'}`, { id });
      return entity;
    } catch (error: any) {
      logger.error(`Failed to find ${this.modelName} by ID / Không thể tìm ${this.modelName} theo ID`, { error, id });
      throw new DatabaseError(`Failed to find ${this.modelName}`, { id, originalError: error });
    }
  }

  /**
Find all entities with optional filtering
   * VI: Tìm tất cả entities với filtering tùy chọn
   */
  async findAll(options?: {
    where?: any;
    orderBy?: any;
    skip?: number;
    take?: number;
    include?: any;
  }): Promise<T[]> {
    try {
      logger.debug(`Finding all ${this.modelName} / Tìm tất cả ${this.modelName}`, options);

      const entities = await (this.prisma as any)[this.modelName].findMany(options || {});

      logger.debug(`Found ${entities.length} ${this.modelName} entities / Đã tìm thấy ${entities.length} ${this.modelName} entities`);
      return entities;
    } catch (error: any) {
      logger.error(`Failed to find all ${this.modelName} / Không thể tìm tất cả ${this.modelName}`, { error, options });
      throw new DatabaseError(`Failed to find ${this.modelName} entities`, { options, originalError: error });
    }
  }

  /**
Create new entity
   * VI: Tạo entity mới
   */
  async create(data: CreateInput): Promise<T> {
    try {
      logger.debug(`Creating new ${this.modelName} / Tạo ${this.modelName} mới`, { data });

      const entity = await (this.prisma as any)[this.modelName].create({
        data,
      });

      logger.debug(`${this.modelName} created successfully / ${this.modelName} đã được tạo thành công`, { id: (entity as any).id });
      return entity;
    } catch (error: any) {
      logger.error(`Failed to create ${this.modelName} / Không thể tạo ${this.modelName}`, { error, data });
      throw new DatabaseError(`Failed to create ${this.modelName}`, { data, originalError: error });
    }
  }

  /**
Update entity by ID
   * VI: Cập nhật entity theo ID
   */
  async update(id: string, data: UpdateInput): Promise<T> {
    try {
      logger.debug(`Updating ${this.modelName} / Cập nhật ${this.modelName}`, { id, data });

      const entity = await (this.prisma as any)[this.modelName].update({
        where: { id },
        data,
      });

      logger.debug(`${this.modelName} updated successfully / ${this.modelName} đã được cập nhật thành công`, { id });
      return entity;
    } catch (error: any) {
      if (error.code === 'P2025') {
        logger.warn(`${this.modelName} not found for update / ${this.modelName} không tìm thấy để cập nhật`, { id });
        throw new DatabaseError(`${this.modelName} not found`, { id });
      }
      logger.error(`Failed to update ${this.modelName} / Không thể cập nhật ${this.modelName}`, { error, id, data });
      throw new DatabaseError(`Failed to update ${this.modelName}`, { id, data, originalError: error });
    }
  }

  /**
Execute transaction with multiple operations
   * VI: Thực thi transaction với nhiều operations
   */
  async transaction<R>(callback: (tx: any) => Promise<R>): Promise<R> {
    try {
      logger.debug(`Starting ${this.modelName} transaction / Bắt đầu transaction ${this.modelName}`);

      const result = await this.prisma.$transaction(async (tx) => {
        return await callback(tx);
      });

      logger.debug(`${this.modelName} transaction completed successfully / Transaction ${this.modelName} đã hoàn thành thành công`);
      return result;
    } catch (error: any) {
      logger.error(`${this.modelName} transaction failed / Transaction ${this.modelName} thất bại`, { error });
      throw new DatabaseError(`${this.modelName} transaction failed`, { originalError: error });
    }
  }
}

Triển Khai Repository Cụ Thể

Example from codebase / Ví dụ từ codebase:

// services/iam-service/src/repositories/user.repository.ts
import { PrismaClient, User } from '@prisma/client';
import { BaseRepository } from '../modules/common/repository';

/**
User repository for database operations
 * VI: Repository người dùng cho các thao tác database
 */
export class UserRepository extends BaseRepository<User, any, any> {
  constructor(prisma: PrismaClient) {
    super(prisma, 'user');
  }

  /**
Find user by email
   * VI: Tìm người dùng theo email
   */
  async findByEmail(email: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { email },
      include: {
        userRoles: {
          include: { role: true },
        },
        userPermissions: {
          include: { permission: true },
        },
      },
    });
  }

  /**
Find user by username
   * VI: Tìm người dùng theo username
   */
  async findByUsername(username: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { username },
    });
  }

  /**
Find user with roles and permissions
   * VI: Tìm người dùng với roles và permissions
   */
  async findWithPermissions(userId: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { id: userId },
      include: {
        userRoles: {
          where: {
            OR: [
              { expiresAt: null },
              { expiresAt: { gte: new Date() } },
            ],
          },
          include: {
            role: {
              include: {
                permissions: {
                  include: { permission: true },
                },
              },
            },
          },
        },
        userPermissions: {
          where: {
            OR: [
              { expiresAt: null },
              { expiresAt: { gte: new Date() } },
            ],
          },
          include: { permission: true },
        },
      },
    });
  }
}

Pattern Upsert

Example from codebase / Ví dụ từ codebase:

// services/iam-service/src/repositories/user-profile.repository.ts
export class UserProfileRepository extends BaseRepository<UserProfile, any, any> {
  /**
Create or update profile for user
   * VI: Tạo hoặc cập nhật profile cho user
   */
  async upsert(userId: string, data: any): Promise<UserProfile> {
    return this.prisma.userProfile.upsert({
      where: { userId },
      update: {
        ...data,
        updatedAt: new Date(),
      },
      create: {
        userId,
        ...data,
      },
      include: {
        user: {
          select: {
            id: true,
            email: true,
            username: true,
          },
        },
      },
    });
  }
}

Pattern Tối Ưu Query

Example from codebase / Ví dụ từ codebase:

// services/iam-service/src/modules/feature/feature.repository.ts
export class FeatureRepository extends BaseRepository<Feature, CreateFeatureInput, UpdateFeatureInput> {
  /**
Find features by tags
   * VI: Tìm features theo tags
   */
  async findByTags(tags: string[]): Promise<Feature[]> {
    try {
      logger.debug('Finding features by tags / Tìm features theo tags', { tags });

      const features = await this.prisma.feature.findMany({
        where: {
          tags: {
            hasSome: tags,
          },
        },
        orderBy: { createdAt: 'desc' },
      });

      logger.debug(`Found ${features.length} features by tags / Đã tìm thấy ${features.length} features theo tags`, { tags });
      return features;
    } catch (error) {
      logger.error('Failed to find features by tags / Không thể tìm features theo tags', { error, tags });
      throw this.handleDatabaseError(error, { tags });
    }
  }
}

Thực Hành Tốt Nhất

Thiết Kế Schema

  • Use appropriate field types (String, Int, Boolean, DateTime, Json) / Sử dụng field types phù hợp
  • Add indexes for frequently queried fields (@@index([email])) / Thêm indexes cho các fields thường query
  • Use relations instead of storing JSON when possible / Sử dụng relations thay vì lưu JSON khi có thể
  • Implement soft deletes with deletedAt field when needed / Triển khai soft deletes với field deletedAt khi cần
  • Use @default() for default values / Sử dụng @default() cho giá trị mặc định
  • Use @updatedAt for automatic timestamp updates / Sử dụng @updatedAt cho cập nhật timestamp tự động

Pattern Repository

  • Extend BaseRepository for common CRUD operations / Kế thừa BaseRepository cho CRUD operations chung
  • Add custom methods for domain-specific queries / Thêm methods tùy chỉnh cho queries đặc thù domain
  • Use include and select to control data fetching / Sử dụng includeselect để kiểm soát việc lấy dữ liệu
  • Handle Prisma errors and convert to domain errors / Xử lý lỗi Prisma và chuyển đổi sang domain errors
  • Log database operations for debugging / Log các thao tác database để debug

Luồng Thực Thi Query / Query Execution Flow

Sơ đồ tuần tự sau minh họa cách các Prisma queries chảy từ lớp ứng dụng đến database:

The following sequence diagram illustrates how Prisma queries flow from the application layer to the database:

sequenceDiagram
    participant App as Application
    participant Repo as Repository
    participant Client as Prisma Client
    participant Pool as Connection Pool
    participant DB as PostgreSQL
    
    App->>Repo: findById(id)
    Repo->>Client: prisma.user.findUnique()
    Client->>Client: Validate query
    Client->>Client: Generate SQL
    Client->>Pool: Request connection
    Pool->>DB: Execute SQL query
    DB-->>Pool: Return results
    Pool-->>Client: Return data
    Client->>Client: Transform to TypeScript types
    Client-->>Repo: Return typed result
    Repo-->>App: Return User | null
    
    Note over App,DB: Prisma ensures type safety<br/>throughout the flow

Tối Ưu Query

  • Use select to fetch only needed fields / Sử dụng select để chỉ lấy các fields cần thiết
  • Implement pagination with skip and take / Triển khai pagination với skiptake
  • Use indexes for frequently queried fields / Sử dụng indexes cho các fields thường query
  • Avoid N+1 queries by using include strategically / Tránh N+1 queries bằng cách sử dụng include chiến lược
  • Use findUnique instead of findFirst when possible / Sử dụng findUnique thay vì findFirst khi có thể
  • Use transactions for multiple related operations / Sử dụng transactions cho nhiều operations liên quan

Xử Lý Lỗi

  • Catch Prisma errors and convert to domain errors / Bắt lỗi Prisma và chuyển đổi sang domain errors
  • Handle unique constraint violations (P2002) / Xử lý vi phạm ràng buộc duy nhất
  • Handle record not found (P2025) / Xử lý không tìm thấy bản ghi
  • Log errors with context / Log lỗi kèm context
  • Provide meaningful error messages / Cung cấp thông báo lỗi có ý nghĩa

Quy Trình Migration / Migration Workflow

Sơ đồ sau mô tả quy trình migration điển hình từ development đến production:

The following diagram shows the typical migration workflow from development to production:

flowchart TD
    Start([Start Migration]) --> EditSchema[Edit schema.prisma]
    EditSchema --> CreateMigration[Run: prisma migrate dev]
    CreateMigration --> GenerateSQL[Prisma generates SQL]
    GenerateSQL --> ReviewSQL{Review SQL?}
    ReviewSQL -->|Yes| CheckSQL[Check migration SQL]
    ReviewSQL -->|No| ApplyDev[Apply to dev database]
    CheckSQL --> ApplyDev
    ApplyDev --> GenerateClient[Generate Prisma Client]
    GenerateClient --> TestDev[Test in development]
    TestDev --> TestPass{Tests pass?}
    TestPass -->|No| FixIssues[Fix issues]
    FixIssues --> EditSchema
    TestPass -->|Yes| CommitMigration[Commit migration files]
    CommitMigration --> DeployProd[Deploy to production]
    DeployProd --> RunDeploy[Run: prisma migrate deploy]
    RunDeploy --> End([Migration Complete])
    
    style Start fill:#e1f5e1
    style End fill:#e1f5e1
    style TestPass fill:#fff4e1
    style ReviewSQL fill:#fff4e1

Migrations

  • Keep migrations small and focused / Giữ migrations nhỏ và tập trung
  • Test migrations before production / Test migrations trước khi production
  • Backup before major changes / Backup trước khi thay đổi lớn
  • Use descriptive migration names / Sử dụng tên migration mô tả
  • Review generated SQL before applying / Xem xét SQL được generate trước khi áp dụng

Quản Lý Kết Nối

  • Use connection pooling for production / Sử dụng connection pooling cho production
  • Close connections on application shutdown / Đóng kết nối khi tắt ứng dụng
  • Handle connection errors gracefully / Xử lý lỗi kết nối một cách lịch sự
  • Monitor connection pool usage / Giám sát việc sử dụng connection pool

Ví Dụ Từ Dự Án

Ví Dụ Schema

Ví Dụ Repository

Ví Dụ Cấu Hình Database

Tham Khảo Nhanh

Các Thao Tác Prisma Thường Dùng

// Tìm theo ID
const user = await prisma.user.findUnique({ where: { id } });

// Tìm với relations
const user = await prisma.user.findUnique({
  where: { id },
  include: { profile: true, roles: true },
});

// Tìm nhiều với filters
const users = await prisma.user.findMany({
  where: { isActive: true },
  skip: 0,
  take: 10,
  orderBy: { createdAt: 'desc' },
});

// Tạo
const user = await prisma.user.create({
  data: { email, passwordHash },
});

// Cập nhật
const user = await prisma.user.update({
  where: { id },
  data: { isActive: false },
});

// Xóa
await prisma.user.delete({ where: { id } });

// Tạo hoặc cập nhật
const profile = await prisma.userProfile.upsert({
  where: { userId },
  update: { ...data },
  create: { userId, ...data },
});

// Transaction
await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ data: userData });
  await tx.userProfile.create({ data: { userId: user.id, ...profileData } });
});

Mã Lỗi Prisma

Code Description / Mô Tả
P2002 Unique constraint violation / Vi phạm ràng buộc duy nhất
P2025 Record not found / Không tìm thấy bản ghi
P2003 Foreign key constraint violation / Vi phạm ràng buộc khóa ngoại
P2014 Required relation violation / Vi phạm quan hệ bắt buộc

Skills Liên Quan

  • API Design: For designing API endpoints that use repositories / Cho thiết kế API endpoints sử dụng repositories
  • Testing Patterns: For testing database operations / Cho test các thao tác database
  • Security: For securing database operations and preventing SQL injection / Cho bảo mật các thao tác database và ngăn chặn SQL injection
  • Observability: For monitoring database performance / Cho giám sát hiệu năng database

Tài Nguyên

Tài Liệu Bên Ngoài

Tài Liệu Nội Bộ