- 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.
422 lines
17 KiB
Markdown
422 lines
17 KiB
Markdown
# 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
|
|
|
|
### 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.
|
|
|
|
```mermaid
|
|
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 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
|
|
|
|
### 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.
|
|
|
|
```mermaid
|
|
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`](../../../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.
|
|
|
|
```typescript
|
|
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`](../../../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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
// 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).
|
|
|
|
```typescript
|
|
// 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**:
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```typescript
|
|
// 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ụ**:
|
|
```typescript
|
|
// ❌ 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ụ**:
|
|
```typescript
|
|
// ❌ 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](../../../services/iam-service/src/modules/common/repository.ts) - Base repository implementation
|
|
- [User Repository](../../../services/iam-service/src/repositories/user.repository.ts) - Example repository implementation
|
|
- [Database Prisma](./database-prisma.md) - Prisma ORM patterns và best practices
|
|
- [Error Handling](./error-handling-patterns.md) - Error handling patterns trong repositories
|