Для чего нужен паттерн Репозиторий (Repository)?

«Для чего нужен паттерн Репозиторий (Repository)?» — вопрос из категории Паттерны, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерн Репозиторий — это слой абстракции между бизнес-логикой приложения и источником данных (БД, внешний API, файловая система). Его основная цель — инкапсулировать всю логику доступа к данным, делая бизнес-слой независимым от деталей инфраструктуры.

Основные цели и преимущества:

  1. Единая точка доступа: Все запросы к сущности User проходят через IUserRepository.
  2. Сокрытие сложности: Бизнес-код не знает, используется ли Entity Framework, Dapper или прямой SQL.
  3. Упрощение тестирования: Репозиторий легко подменить mock-объектом для модульного тестирования бизнес-логики.
  4. Централизация правил: Сложные запросы (например, с фильтрацией и пагинацией) не размазываются по коду.

Пример реализации с Generic Repository и спецификациями:

// 1. Базовый интерфейс репозитория
public interface IRepository<T> where T : class
{
    Task<T?> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    void Update(T entity);
    void Delete(T entity);
    Task<IEnumerable<T>> FindAsync(ISpecification<T> spec); // Для сложных запросов
}

// 2. Конкретная реализация для Entity Framework Core
public class EfRepository<T> : IRepository<T> where T : class
{
    private readonly AppDbContext _context;
    public EfRepository(AppDbContext context) => _context = context;

    public async Task<T?> GetByIdAsync(int id) => await _context.Set<T>().FindAsync(id);
    public async Task AddAsync(T entity) => await _context.Set<T>().AddAsync(entity);
    // ... остальные методы
}

// 3. Использование в сервисе
public class UserService
{
    private readonly IRepository<User> _userRepo;
    public UserService(IRepository<User> userRepo) => _userRepo = userRepo; // Внедрение зависимости

    public async Task<User?> GetActiveUserByEmail(string email)
    {
        // Бизнес-логика изолирована от деталей запроса
        var spec = new ActiveUserByEmailSpecification(email);
        return (await _userRepo.FindAsync(spec)).FirstOrDefault();
    }
}

Важно: В современных приложениях на EF Core часто обходятся без "классического" репозитория, используя DbSet напрямую в сервисах, но инкапсулируя сложные запросы в спецификации (Specification pattern) или запросы (Query objects) для сохранения преимуществ тестируемости и управляемости.