Files
pos-system/docs/en/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

221 lines
6.8 KiB
Markdown

---
name: repository-pattern
description: Repository pattern implementation and best practices for GoodGo microservices. Use when implementing data access layers, extending BaseRepository, writing database queries, handling transactions, or optimizing database operations.
---
# Repository Pattern
## When to Use This Skill
Use this skill when:
- Implementing data access layers for new modules
- Extending BaseRepository for specific entity types
- Writing custom database queries
- Handling database transactions
- Optimizing database queries and operations
- Testing repository implementations
- Organizing data access code
## Core Concepts
### Repository Pattern Benefits
1. **Abstraction**: Separates business logic from data access
2. **Testability**: Easy to mock repositories for testing
3. **Maintainability**: Centralized database operations
4. **Consistency**: Standardized CRUD operations
5. **Type Safety**: TypeScript generics provide type safety
### BaseRepository Class
The `BaseRepository` abstract class provides common database operations that can be extended:
- `findById(id)` - Find entity by ID
- `findByUnique(field, value)` - Find by unique field
- `findAll(options)` - Find all with filtering, pagination, sorting
- `create(data)` - Create new entity
- `update(id, data)` - Update entity
- `delete(id)` - Delete entity
- `count(where)` - Count entities
- `exists(id)` - Check if entity exists
- `transaction(callback)` - Execute transaction
## Patterns
### Extending BaseRepository
```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');
}
// Add custom methods
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({
where: { email },
});
}
async findByUsername(username: string): Promise<User | null> {
return this.prisma.user.findUnique({
where: { username },
});
}
}
```
### Custom Query Methods
Add domain-specific query methods:
```typescript
export class UserRepository extends BaseRepository<User, CreateUserInput, UpdateUserInput> {
// Find user with related data
async findWithPermissions(userId: string): Promise<User | null> {
return this.prisma.user.findUnique({
where: { id: userId },
include: {
userRoles: {
include: { role: true },
},
userPermissions: {
include: { permission: true },
},
},
});
}
// Complex query with filtering
async findActiveUsers(organizationId?: string): Promise<User[]> {
return this.prisma.user.findMany({
where: {
isActive: true,
...(organizationId && { organizationId }),
},
orderBy: { createdAt: 'desc' },
});
}
}
```
### Using Repository Interface
Implement the `IRepository` interface for type safety:
```typescript
import { IRepository } from '../modules/common/repository';
export class UserRepository
extends BaseRepository<User, CreateUserInput, UpdateUserInput>
implements IRepository<User, CreateUserInput, UpdateUserInput> {
// Implementation...
}
```
### Error Handling
BaseRepository handles errors automatically:
```typescript
async findById(id: string): Promise<T | null> {
try {
// Database operation
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 repository transaction method for multiple operations:
```typescript
await repository.transaction(async (tx) => {
const user = await tx.user.create({ data: userData });
await tx.userProfile.create({ data: { userId: user.id, ...profileData } });
return user;
});
```
### Query Options
Use Prisma query options in findAll:
```typescript
// Pagination
const users = await userRepository.findAll({
skip: (page - 1) * limit,
take: limit,
});
// Filtering
const activeUsers = await userRepository.findAll({
where: { isActive: true },
});
// Sorting
const recentUsers = await userRepository.findAll({
orderBy: { createdAt: 'desc' },
});
// Including relations
const usersWithRoles = await userRepository.findAll({
include: { userRoles: true },
});
```
## Best Practices
1. **Extend BaseRepository**: Always extend BaseRepository instead of implementing from scratch
2. **Custom Methods**: Add domain-specific query methods in repository subclasses
3. **Type Safety**: Use TypeScript generics for type safety
4. **Error Handling**: Let BaseRepository handle common errors, handle domain-specific errors in custom methods
5. **Logging**: BaseRepository handles logging automatically
6. **Transactions**: Use repository transaction method for multi-step operations
7. **Query Optimization**: Use Prisma query options (select, include) to optimize queries
8. **Single Responsibility**: Each repository handles one entity type
9. **Dependency Injection**: Inject PrismaClient in constructor for testability
## Common Mistakes
1. **Not Extending BaseRepository**: Implementing CRUD from scratch instead of extending
2. **Business Logic in Repository**: Putting business logic in repository instead of service layer
3. **Exposing Prisma Client**: Exposing raw Prisma client instead of using repository methods
4. **Missing Error Handling**: Not handling errors in custom query methods
5. **Over-fetching Data**: Using `include` unnecessarily, fetching too much data
6. **No Type Safety**: Not using TypeScript generics properly
7. **Transaction Mistakes**: Not using repository transaction method for related operations
## Troubleshooting
### Type Errors with Prisma
**Problem**: TypeScript errors when using Prisma client methods
**Solution**: Ensure Prisma client is generated: `pnpm prisma generate`. Use proper type assertions if needed.
### Transaction Rollback Issues
**Problem**: Transaction not rolling back on error
**Solution**: Ensure all operations in transaction callback use the transaction client (`tx`) parameter, not the main Prisma client.
### Performance Issues
**Problem**: Slow queries or N+1 query problems
**Solution**: Use `include` to fetch related data in single query. Use `select` to limit fields. Add database indexes.
## Resources
- [BaseRepository](../../services/iam-service/src/modules/common/repository.ts) - Base repository implementation
- [User Repository](../../services/iam-service/src/repositories/user.repository.ts) - Example repository
- [Database Prisma](../database-prisma/SKILL.md) - Prisma ORM patterns
- [Error Handling](../error-handling-patterns/SKILL.md) - Error handling in repositories