- 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.
17 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
- Abstraction / Trừu Tượng Hóa: Tách biệt business logic khỏi data access
- Testability / Khả Năng Test: Dễ dàng mock repositories để test
- Maintainability / Khả Năng Bảo Trì: Centralized database operations
- Consistency / Tính Nhất Quán: Standardized CRUD operations
- Type Safety / An Toàn Kiểu: TypeScript generics cung cấp type safety
Kiến Trúc Repository
Repository pattern tạo abstraction layer giữa service layer và data access layer, cung cấp sự tách biệt rõ ràng giữa các concerns.
graph TB
Controller["Controller<br/>(HTTP Handler)"] --> Service["Service Layer<br/>(Business Logic)"]
Service --> Repository["Repository<br/>(Data Access)"]
Repository --> Prisma["Prisma Client<br/>(ORM)"]
Prisma --> Database[("Database<br/>(PostgreSQL)")]
style Controller fill:#e1f5ff
style Service fill:#fff4e1
style Repository fill:#e8f5e9
style Prisma fill:#f3e5f5
style Database fill:#ffebee
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 IDfindByUnique(field, value)- Find by unique field / Tìm theo field duy nhấtfindAll(options)- Find all with filtering, pagination, sorting / Tìm tất cả với filtering, pagination, sortingcreate(data)- Create new entity / Tạo entity mớiupdate(id, data)- Update entity / Cập nhật entitydelete(id)- Delete entity / Xóa entitycount(where)- Count entities / Đếm entitiesexists(id)- Check if entity exists / Kiểm tra entity có tồn tạitransaction(callback)- Execute transaction / Thực thi transaction
Cấu Trúc Class Hierarchy
Repositories extend abstract class BaseRepository, kế thừa các CRUD operations chung trong khi cho phép thêm custom query methods.
classDiagram
class BaseRepository {
<<abstract>>
#prisma: PrismaClient
#modelName: string
+findById(id: string) Promise~T~null~
+findByUnique(field: string, value: any) Promise~T~null~
+findAll(options?: any) Promise~T[]~
+create(data: CreateInput) Promise~T~
+update(id: string, data: UpdateInput) Promise~T~
+delete(id: string) Promise~boolean~
+count(where?: any) Promise~number~
+exists(id: string) Promise~boolean~
+transaction(callback: Function) Promise~R~
}
class IRepository {
<<interface>>
+findById(id: string) Promise~T~null~
+findByUnique(field: string, value: any) Promise~T~null~
+findAll(options?: any) Promise~T[]~
+create(data: CreateInput) Promise~T~
+update(id: string, data: UpdateInput) Promise~T~
+delete(id: string) Promise~boolean~
+count(where?: any) Promise~number~
+exists(id: string) Promise~boolean~
}
class UserRepository {
+findByEmail(email: string) Promise~User~null~
+findByUsername(username: string) Promise~User~null~
+findWithPermissions(userId: string) Promise~User~null~
+findActiveUsers(organizationId?: string) Promise~User[]~
}
class ProductRepository {
+findByCategory(categoryId: string) Promise~Product[]~
+findActiveProducts() Promise~Product[]~
}
BaseRepository <|-- UserRepository : extends
BaseRepository <|-- ProductRepository : extends
IRepository <|.. UserRepository : implements
Lưu ý: Các repositories cụ thể như UserRepository và ProductRepository extend BaseRepository và có thể implement interface IRepository để có thêm type safety. Chúng kế thừa tất cả base CRUD methods và thêm các domain-specific query methods.
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;
});
Transaction Flow / Luồng Transaction:
sequenceDiagram
participant Service
participant Repository
participant Prisma as Prisma Client
participant DB as Database
Service->>Repository: transaction(callback)
Repository->>Prisma: $transaction(callback)
Prisma->>DB: BEGIN TRANSACTION
Note over Service,DB: All operations use transaction client (tx)<br/>Tất cả operations sử dụng transaction client (tx)
Service->>Repository: tx.user.create(data)
Repository->>Prisma: tx.user.create(data)
Prisma->>DB: INSERT INTO users ...
DB-->>Prisma: User created
Prisma-->>Repository: User entity
Repository-->>Service: User entity
Service->>Repository: tx.userProfile.create(data)
Repository->>Prisma: tx.userProfile.create(data)
Prisma->>DB: INSERT INTO user_profiles ...
DB-->>Prisma: Profile created
Prisma-->>Repository: Profile entity
Repository-->>Service: Profile entity
alt All operations succeed / Tất cả operations thành công
Prisma->>DB: COMMIT
DB-->>Prisma: Transaction committed
Prisma-->>Repository: Success result
Repository-->>Service: Success result
else Error occurs / Lỗi xảy ra
Prisma->>DB: ROLLBACK
DB-->>Prisma: Transaction rolled back
Prisma-->>Repository: Error thrown
Repository-->>Service: DatabaseError thrown
end
Important / Quan Trọng: Tất cả operations trong transaction callback phải sử dụng transaction client (tx) parameter, không phải main Prisma client, để đảm bảo tính atomic.
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
- Extend BaseRepository / Mở Rộng BaseRepository: Luôn extend BaseRepository thay vì implement từ đầu
- Custom Methods / Methods Tùy Chỉnh: Thêm domain-specific query methods trong repository subclasses
- Type Safety / An Toàn Kiểu: Sử dụng TypeScript generics cho type safety
- Error Handling / Xử Lý Lỗi: Để BaseRepository xử lý common errors, xử lý domain-specific errors trong custom methods
- Logging / Ghi Log: BaseRepository xử lý logging tự động
- Transactions / Transactions: Sử dụng repository transaction method cho multi-step operations
- Query Optimization / Tối Ưu Query: Sử dụng Prisma query options (select, include) để optimize queries
- Single Responsibility / Trách Nhiệm Đơn: Mỗi repository xử lý một entity type
- Dependency Injection / Tiêm Phụ Thuộc: Inject PrismaClient trong constructor để dễ test
- Avoid Business Logic / Tránh Business Logic: Không đặt business logic trong repository, đặt trong service layer
Lỗi Thường Gặp
- Not Extending BaseRepository / Không Mở Rộng BaseRepository: Implement CRUD từ đầu thay vì extend
- Business Logic in Repository / Business Logic Trong Repository: Đặt business logic trong repository thay vì service layer
- Exposing Prisma Client / Tiết Lộ Prisma Client: Expose raw Prisma client thay vì sử dụng repository methods
- Missing Error Handling / Thiếu Xử Lý Lỗi: Không xử lý errors trong custom query methods
- Over-fetching Data / Lấy Quá Nhiều Dữ Liệu: Sử dụng
includekhông cần thiết, fetch quá nhiều data - No Type Safety / Không An Toàn Kiểu: Không sử dụng TypeScript generics đúng cách
- Transaction Mistakes / Lỗi Transaction: Không sử dụng repository transaction method cho related operations
- N+1 Query Problems / Vấn Đề N+1 Query: Fetch related data trong loops thay vì sử dụng include
- 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
- BaseRepository - Base repository implementation
- User Repository - Example repository implementation
- Database Prisma - Prisma ORM patterns và best practices
- Error Handling - Error handling patterns trong repositories