Репозитории

Есть вещи, которые я использую почти в каждом проекте. Репозитории - одна из них. Порой приходится жертвовать этим паттернов в угоду скорости реализации, но при первой возможности я сразу его внедряю. Люблю этот паттерн. Он создает прослойку между твоим кодом и хранилищем данных. Будь то MySQL, Redis, API или даже просто файл.


Что даёт репозиторий

Простой пример. Контроллеры и сервисы не знают, где лежат данные. В БД, в Redis, во внешнем API. Им всё равно.

// Сервис не знает, откуда берутся пользователи
class RegisterService
{
    public function __construct(
        private UserRepositoryInterface $users
    ) {}
    
    public function register(string $email): void
    {
        $user = $this->users->findByEmail($email);
        // ...
    }
}

Создаем сервис, определяем в качестве аргумента UserRepositoryInterface и у нас теперь есть абстракция, не привязанная к конкретному хранилищу данных. Теперь нам нужно реализовать репозиторий, подходящий под этот интерфейс.

// Так
class MysqlUserRepository implements UserRepositoryInterface { ... }

// Или так
class RedisUserRepository implements UserRepositoryInterface { ... }

Решили переехать с MySQL на PostgreSQL? Или добавить кэш через Redis? Меняешь реализацию репозитория в одном месте. Сервисы не трогаешь.

Использование репозиториев позволяет:

  • Легко менять хранилище
  • Упрощаешь тестирование
  • Соблюдаешь единую ответственность

Важные правила

1. Репозиторий всегда делается под интерфейс

Никогда не привязывайся к конкретной реализации. Код должен работать с UserRepositoryInterface, а не с MysqlUserRepository

// Плохо - привязка к конкретной реализации
class RegisterService
{
    public function __construct(
        private MysqlUserRepository $users
    ) {}
}

// Хорошо - работа через интерфейс
class RegisterService
{
    public function __construct(
        private UserRepositoryInterface $users
    ) {}
}

2. Только стандартные операции с сущностью

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function findAll(): array;
    public function save(User $user): void;
    public function delete(User $user): void;
    public function existsById(int $id): bool;
}

❌ Не стоит (слишком специфичные запросы)

interface UserRepositoryInterface
{
    // Плохо — бизнес-логика в репозитории
    public function findActiveUsersWithOrders(): array;
    public function getTop10(): array;
    public function findUsersWhoBoughtProduct(int $productId): array;
}

🤔 Как быть, если нужен getTop10?

Рекомендую добавить метод с критериями. Вот как это может выглядеть.

interface UserRepositoryInterface
{
    public function findByCriteria(
        array $criteria, 
        array $orderBy = [], 
        int $limit = 100
    ): array;
}

// В сервисе
class UserService
{
    public function getTop10(): array
    {
        return $this->users->findByCriteria(
            criteria: ['status' => 'active'],
            orderBy: ['rating' => 'DESC'],
            limit: 10
        );
    }
}

Использование критериев позволяет использовать один метод вместо условных 10. Просто меняй критерии и получай нужные данные.

Итог

Репозитории - это не серебряная пуля. Но для большинства проектов они делают код чище и гибче.

Что даёт репозиторий Как это помогает
Отделение данных от логики Легко менять хранилище
Единое место для запросов Не дублируешь код
Упрощение тестов Моки вместо реальной БД
Чёткая ответственность Код становится понятнее

Обсудить пост:

🔥 И не забудь подписаться :)