---
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

### Repository Architecture

The repository pattern creates an abstraction layer between the service layer and data access layer, providing a clean separation of 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
```

### 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

### Class Hierarchy

Repositories extend the `BaseRepository` abstract class, inheriting common CRUD operations while allowing 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
```

**Note**: Specific repositories like `UserRepository` and `ProductRepository` extend `BaseRepository` and can implement the `IRepository` interface for additional type safety. They inherit all base CRUD methods and add domain-specific query methods.

## 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;
});
```

**Transaction Flow**:

```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)
    
    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
        Prisma->>DB: COMMIT
        DB-->>Prisma: Transaction committed
        Prisma-->>Repository: Success result
        Repository-->>Service: Success result
    else Error occurs
        Prisma->>DB: ROLLBACK
        DB-->>Prisma: Transaction rolled back
        Prisma-->>Repository: Error thrown
        Repository-->>Service: DatabaseError thrown
    end
```

**Important**: All operations within the transaction callback must use the transaction client (`tx`) parameter, not the main Prisma client, to ensure atomicity.

### 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
