Какую роль выполняет паттерн Репозиторий (Repository) в архитектуре приложения

Ответ

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

Ключевые цели и преимущества:

  • Изоляция от источника данных: Бизнес-логика не знает, откуда берутся данные — из SQL-базы, NoSQL-хранилища или внешнего API. Это позволяет легко заменять источник данных без изменения основного кода.
  • Централизация логики запросов: Все запросы к данным (фильтрация, сортировка, выборка) инкапсулируются внутри репозитория, что предотвращает их дублирование по всему приложению.
  • Упрощение тестирования: Бизнес-логику можно тестировать изолированно, подменив реальный репозиторий на mock-объект (in-memory реализацию), который не требует подключения к базе данных.
  • Повышение читаемости кода: Код сервисного слоя становится чище, так как вместо SQL-запросов или вызовов ORM он оперирует понятными методами, например, user_repository.get_by_email(email).

Упрощенный пример на Python:

from abc import ABC, abstractmethod
from dataclasses import dataclass

# Модель данных (доменный объект)
@dataclass
class User:
    id: int
    username: str

# Абстрактный репозиторий (интерфейс)
class AbstractUserRepository(ABC):
    @abstractmethod
    def get_by_id(self, user_id: int) -> User | None:
        raise NotImplementedError

    @abstractmethod
    def add(self, user: User) -> None:
        raise NotImplementedError

# Конкретная реализация для работы с БД (например, PostgreSQL)
class PostgresUserRepository(AbstractUserRepository):
    def __init__(self, db_connection):
        self.connection = db_connection

    def get_by_id(self, user_id: int) -> User | None:
        # Здесь будет реальный SQL-запрос
        print(f"[DB] Получение пользователя с id={user_id} из PostgreSQL")
        # cursor.execute("SELECT id, username FROM users WHERE id = %s", (user_id,))
        # ... логика получения данных
        if user_id == 1:
            return User(id=1, username="john_doe")
        return None

    def add(self, user: User) -> None:
        print(f"[DB] Добавление пользователя {user.username} в PostgreSQL")
        # cursor.execute("INSERT INTO ...")

# Сервисный слой, который использует репозиторий
class UserService:
    def __init__(self, user_repo: AbstractUserRepository):
        self.user_repo = user_repo

    def find_user(self, user_id: int):
        user = self.user_repo.get_by_id(user_id)
        if user:
            return f"Найден пользователь: {user.username}"
        return "Пользователь не найден"

# Использование
db_conn = "..." # Условное подключение к БД
repo = PostgresUserRepository(db_conn)
service = UserService(repo)

print(service.find_user(1))