Files
pos-system/docs/vi/skills/repository-pattern.md
Ho Ngoc Hai 9b6c585f57 Enhance documentation structure and improve bilingual support across skills
- Updated skill documentation files to include structured metadata for better organization.
- Enhanced bilingual descriptions and guidelines for clarity in both English and Vietnamese.
- Refined sections on usage, best practices, and related skills to ensure consistency across all documentation.
- Improved formatting and removed outdated references to streamline the documentation experience.
- Added best practices checklists to relevant skills for better usability and adherence to standards.
2026-01-01 07:35:44 +07:00

13 KiB

Pattern Repository

Repository pattern implementation and best practices for GoodGo microservices including BaseRepository extension, custom queries, transactions, and query optimization.

Implementation và best practices về repository pattern cho GoodGo microservices bao gồm mở rộng BaseRepository, custom queries, transactions, và query optimization.

Tổng Quan

The Repository Pattern provides an abstraction layer between business logic and data access, making code more maintainable, testable, and consistent. This guide covers how to implement repositories using the BaseRepository class, write custom queries, handle transactions, and optimize database operations in GoodGo microservices.

Repository Pattern cung cấp abstraction layer giữa business logic và data access, làm cho code dễ bảo trì, testable, và nhất quán hơn. Hướng dẫn này bao gồm cách implement repositories sử dụng BaseRepository class, viết custom queries, xử lý transactions, và optimize database operations trong GoodGo microservices.

Khi Nào Sử Dụng

Use the repository pattern when:

  • Implementing data access layers for new modules
  • Extending BaseRepository for specific entity types
  • Writing custom database queries beyond standard CRUD
  • Handling database transactions for multiple operations
  • Optimizing database queries and operations
  • Testing repository implementations
  • Organizing data access code consistently

Sử dụng repository pattern khi:

  • Implement data access layers cho modules mới
  • Mở rộng BaseRepository cho các entity types cụ thể
  • Viết custom database queries ngoài CRUD chuẩn
  • Xử lý database transactions cho nhiều operations
  • Optimize database queries và operations
  • Testing repository implementations
  • Tổ chức data access code một cách nhất quán

Khái Niệm Chính

Lợi Ích Của Repository Pattern

  1. Abstraction / Trừu Tượng Hóa: Tách biệt business logic khỏi data access
  2. Testability / Khả Năng Test: Dễ dàng mock repositories để test
  3. Maintainability / Khả Năng Bảo Trì: Centralized database operations
  4. Consistency / Tính Nhất Quán: Standardized CRUD operations
  5. Type Safety / An Toàn Kiểu: TypeScript generics cung cấp type safety

Class BaseRepository

The BaseRepository abstract class provides common database operations that can be extended by specific repositories. It handles error handling, logging, and provides type-safe methods for CRUD operations.

Class abstract BaseRepository cung cấp các database operations chung có thể được mở rộng bởi các repositories cụ thể. Nó xử lý error handling, logging, và cung cấp các methods type-safe cho CRUD operations.

Available Methods / Các Methods Có Sẵn:

  • findById(id) - Find entity by ID / Tìm entity theo ID
  • findByUnique(field, value) - Find by unique field / Tìm theo field duy nhất
  • findAll(options) - Find all with filtering, pagination, sorting / Tìm tất cả với filtering, pagination, sorting
  • create(data) - Create new entity / Tạo entity mới
  • update(id, data) - Update entity / Cập nhật entity
  • delete(id) - Delete entity / Xóa entity
  • count(where) - Count entities / Đếm entities
  • exists(id) - Check if entity exists / Kiểm tra entity có tồn tại
  • transaction(callback) - Execute transaction / Thực thi transaction

Tham Khảo: services/iam-service/src/modules/common/repository.ts

Patterns

Mở Rộng BaseRepository

Create a repository by extending BaseRepository and passing the Prisma model name.

Tạo repository bằng cách mở rộng BaseRepository và truyền tên Prisma model.

import { PrismaClient, User } from '@prisma/client';
import { BaseRepository } from '../modules/common/repository';

export class UserRepository extends BaseRepository<User, CreateUserInput, UpdateUserInput> {
  constructor(prisma: PrismaClient) {
    super(prisma, 'user');
  }

// Thêm các methods tùy chỉnh
  async findByEmail(email: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { email },
    });
  }
}

Tham Khảo: services/iam-service/src/repositories/user.repository.ts

Các Methods Query Tùy Chỉnh

Add domain-specific query methods for complex queries that go beyond standard CRUD operations.

Thêm các methods query domain-specific cho các queries phức tạp vượt quá CRUD operations chuẩn.

export class UserRepository extends BaseRepository<User, CreateUserInput, UpdateUserInput> {
// Tìm user với dữ liệu liên quan
  async findWithPermissions(userId: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { id: userId },
      include: {
        userRoles: {
          include: { role: true },
        },
        userPermissions: {
          include: { permission: true },
        },
      },
    });
  }

// Query phức tạp với filtering
  async findActiveUsers(organizationId?: string): Promise<User[]> {
    return this.prisma.user.findMany({
      where: {
        isActive: true,
        ...(organizationId && { organizationId }),
      },
      orderBy: { createdAt: 'desc' },
    });
  }
}

Sử Dụng Repository Interface

Implement the IRepository interface for additional type safety and consistency.

Implement interface IRepository để có thêm type safety và tính nhất quán.

import { IRepository } from '../modules/common/repository';

export class UserRepository 
  extends BaseRepository<User, CreateUserInput, UpdateUserInput>
  implements IRepository<User, CreateUserInput, UpdateUserInput> {
  
  constructor(prisma: PrismaClient) {
    super(prisma, 'user');
  }
  
  // Implementation...
}

Xử Lý Lỗi

BaseRepository handles errors automatically, wrapping database operations in try-catch blocks and converting errors to DatabaseError instances.

BaseRepository xử lý errors tự động, bọc các database operations trong try-catch blocks và chuyển đổi errors thành DatabaseError instances.

// BaseRepository tự động xử lý errors
async findById(id: string): Promise<T | null> {
  try {
    const entity = await this.prisma.user.findUnique({ where: { id } });
    return entity;
  } catch (error: any) {
    logger.error(`Failed to find ${this.modelName} by ID`, { error, id });
    throw new DatabaseError(`Failed to find ${this.modelName}`, { id, originalError: error });
  }
}

Transactions

Use the repository transaction method for operations that need to be atomic (all succeed or all fail).

Sử dụng method transaction của repository cho các operations cần atomic (tất cả thành công hoặc tất cả thất bại).

// Thực thi nhiều operations trong transaction
await userRepository.transaction(async (tx) => {
  const user = await tx.user.create({ data: userData });
  await tx.userProfile.create({ 
    data: { userId: user.id, ...profileData } 
  });
  return user;
});

Important / Quan Trọng: Sử dụng transaction client (tx) parameter cho tất cả operations, không phải main Prisma client.

Các Tùy Chọn Query

Use Prisma query options in findAll for filtering, pagination, sorting, and including relations.

Sử dụng các tùy chọn query của Prisma trong findAll để filtering, pagination, sorting, và include relations.

// Phân trang
const users = await userRepository.findAll({
  skip: (page - 1) * limit,
  take: limit,
});

// Lọc
const activeUsers = await userRepository.findAll({
  where: { isActive: true },
});

// Sắp xếp
const recentUsers = await userRepository.findAll({
  orderBy: { createdAt: 'desc' },
});

// Bao gồm relations
const usersWithRoles = await userRepository.findAll({
  include: { userRoles: true },
});

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

  1. Extend BaseRepository / Mở Rộng BaseRepository: Luôn extend BaseRepository thay vì implement từ đầu
  2. Custom Methods / Methods Tùy Chỉnh: Thêm domain-specific query methods trong repository subclasses
  3. Type Safety / An Toàn Kiểu: Sử dụng TypeScript generics cho type safety
  4. Error Handling / Xử Lý Lỗi: Để BaseRepository xử lý common errors, xử lý domain-specific errors trong custom methods
  5. Logging / Ghi Log: BaseRepository xử lý logging tự động
  6. Transactions / Transactions: Sử dụng repository transaction method cho multi-step operations
  7. Query Optimization / Tối Ưu Query: Sử dụng Prisma query options (select, include) để optimize queries
  8. Single Responsibility / Trách Nhiệm Đơn: Mỗi repository xử lý một entity type
  9. Dependency Injection / Tiêm Phụ Thuộc: Inject PrismaClient trong constructor để dễ test
  10. Avoid Business Logic / Tránh Business Logic: Không đặt business logic trong repository, đặt trong service layer

Lỗi Thường Gặp

  1. Not Extending BaseRepository / Không Mở Rộng BaseRepository: Implement CRUD từ đầu thay vì extend
  2. Business Logic in Repository / Business Logic Trong Repository: Đặt business logic trong repository thay vì service layer
  3. Exposing Prisma Client / Tiết Lộ Prisma Client: Expose raw Prisma client thay vì sử dụng repository methods
  4. Missing Error Handling / Thiếu Xử Lý Lỗi: Không xử lý errors trong custom query methods
  5. Over-fetching Data / Lấy Quá Nhiều Dữ Liệu: Sử dụng include không cần thiết, fetch quá nhiều data
  6. No Type Safety / Không An Toàn Kiểu: Không sử dụng TypeScript generics đúng cách
  7. Transaction Mistakes / Lỗi Transaction: Không sử dụng repository transaction method cho related operations
  8. N+1 Query Problems / Vấn Đề N+1 Query: Fetch related data trong loops thay vì sử dụng include
  9. Missing Indexes / Thiếu Indexes: Không thêm database indexes cho các fields thường query

Xử Lý Sự Cố

Lỗi Kiểu Với Prisma

Problem / Vấn Đề: TypeScript errors khi sử dụng Prisma client methods

Solution / Giải Pháp:

  • Đảm bảo Prisma client đã được generate: pnpm prisma generate
  • Sử dụng type assertions nếu cần: (this.prisma as any)[this.modelName]
  • Verify Prisma schema matches TypeScript types

Vấn Đề Rollback Transaction

Problem / Vấn Đề: Transaction không rollback khi có lỗi

Solution / Giải Pháp:

  • Đảm bảo tất cả operations trong transaction callback sử dụng transaction client (tx) parameter
  • Không sử dụng main Prisma client trong transaction callback
  • Let errors propagate, transaction will rollback automatically

Example / Ví Dụ:

// ❌ Bad: Using main client in transaction
await repository.transaction(async (tx) => {
  await repository.prisma.user.create(...); // Wrong!
});

// ✅ Good: Using transaction client
await repository.transaction(async (tx) => {
  await tx.user.create(...); // Correct!
});

Vấn Đề Hiệu Suất

Problem / Vấn Đề: Queries chậm hoặc N+1 query problems

Solution / Giải Pháp:

  • Sử dụng include để fetch related data trong single query
  • Sử dụng select để limit fields được fetch
  • Thêm database indexes cho các fields thường query
  • Avoid fetching unnecessary relations

Example / Ví Dụ:

// ❌ Bad: N+1 query problem
const users = await userRepository.findAll();
for (const user of users) {
  const roles = await roleRepository.findByUserId(user.id); // N queries!
}

// ✅ Good: Single query with include
const users = await userRepository.findAll({
  include: { userRoles: { include: { role: true } } },
});

Tên Model Không Khớp

Problem / Vấn Đề: Model name không khớp với Prisma schema

Solution / Giải Pháp:

  • Verify model name matches Prisma schema exactly (case-sensitive)
  • Use camelCase for model names (e.g., 'user', 'userProfile', 'accessRequest')
  • Check Prisma schema for correct model names

Tài Nguyên