Как паттерн Repository применяется для работы с базами данных?

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

Ответ

Паттерн Repository представляет собой слой абстракции между бизнес-логикой приложения и хранилищем данных (например, базой данных). Он инкапсулирует логику доступа к данным, предоставляя унифицированный интерфейс для выполнения CRUD-операций (Create, Read, Update, Delete) над сущностями.

Почему используется Repository?

  • Изоляция: Отделяет бизнес-логику от деталей реализации конкретной СУБД или ORM, что упрощает смену хранилища данных.
  • Тестируемость: Позволяет легко подменять реальный репозиторий на mock-объекты при юнит-тестировании бизнес-логики.
  • Централизация: Предоставляет единую точку управления операциями с определенной сущностью, улучшая читаемость и поддерживаемость кода.
  • Чистая архитектура: Способствует созданию более чистой и модульной архитектуры приложения.

Пример реализации на Python (с использованием ORM-сессии, например, SQLAlchemy):

from sqlalchemy.orm import Session # Пример для SQLAlchemy
from typing import List, Optional

# Предположим, у нас есть модель User
class User:
    def __init__(self, id: int, name: str, email: str):
        self.id = id
        self.name = name
        self.email = email

    def __repr__(self):
        return f"<User(id={self.id}, name='{self.name}')>"

class UserRepository:
    def __init__(self, session: Session):
        self.session = session

    def get_by_id(self, user_id: int) -> Optional[User]:
        # В реальном ORM здесь был бы запрос к БД
        return self.session.query(User).filter_by(id=user_id).first()

    def add(self, user: User):
        self.session.add(user)
        self.session.commit()

    def update(self, user: User):
        # В ORM изменения отслеживаются автоматически после add/query
        self.session.merge(user) # Для отсоединенных объектов
        self.session.commit()

    def delete(self, user: User):
        self.session.delete(user)
        self.session.commit()

    def get_all(self) -> List[User]:
        return self.session.query(User).all()

    def find_by_email(self, email: str) -> Optional[User]:
        return self.session.query(User).filter_by(email=email).first()

Нюансы и лучшие практики:

  • Разделение ответственности: Репозиторий должен заниматься только доступом к данным, не смешивая бизнес-логику.
  • Сложные запросы: Для специфических или сложных запросов можно добавлять специализированные методы (например, filter_by_status, find_active_users).
  • Unit of Work: Паттерн Repository часто используется в связке с паттерном Unit of Work для управления транзакциями и группировки изменений перед их сохранением.