← Назад к вопросам

Что такое паттерн Репозиторий (Repository)?

2.8 Senior🔥 111 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Паттерн Репозиторий (Repository Pattern)

Repository Pattern — это архитектурный паттерн, который абстрагирует работу с хранилищем данных (базой данных, файлами, кэшем). Репозиторий действует как посредник между бизнес-логикой (domain layer) и источником данных (data layer), предоставляя методы для CRUD операций (Create, Read, Update, Delete).

Основная идея

Вместо того чтобы разбросать SQL-запросы по всему коду, мы создаём специальный класс (репозиторий), который отвечает за все операции с одним типом данных. Это делает код:

  • Более тестируемым — легко мокировать БД
  • Более читаемым — вся логика работы с данными в одном месте
  • Более гибким — легко менять источник данных

Простой пример

# ❌ БЕЗ Repository Pattern — SQL везде
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
engine = create_engine(sqlite:///db.sqlite)
Session = sessionmaker(bind=engine)

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

# Логика разбросана по коду
session = Session()
user = session.query(User).filter(User.id == 1).first()
user.name = "Новое имя"
session.commit()

# ✓ С Repository Pattern — всё в репозитории
class UserRepository:
    """Репозиторий для работы с пользователями"""
    
    def __init__(self, session):
        self.session = session
    
    def get_by_id(self, user_id: int) -> User:
        """Получить пользователя по ID"""
        return self.session.query(User).filter(User.id == user_id).first()
    
    def get_by_email(self, email: str) -> User:
        """Получить пользователя по email"""
        return self.session.query(User).filter(User.email == email).first()
    
    def get_all(self) -> list[User]:
        """Получить всех пользователей"""
        return self.session.query(User).all()
    
    def create(self, name: str, email: str) -> User:
        """Создать новго пользователя"""
        user = User(name=name, email=email)
        self.session.add(user)
        self.session.commit()
        return user
    
    def update(self, user_id: int, name: str, email: str) -> User:
        """Обновить пользователя"""
        user = self.get_by_id(user_id)
        if user:
            user.name = name
            user.email = email
            self.session.commit()
        return user
    
    def delete(self, user_id: int) -> bool:
        """Удалить пользователя"""
        user = self.get_by_id(user_id)
        if user:
            self.session.delete(user)
            self.session.commit()
            return True
        return False

# Теперь используем репозиторий
repo = UserRepository(session)
user = repo.get_by_id(1)
repo.update(1, "Иван Петров", "ivan@example.com")

Пример с интерфейсом (Interface/ABC)

from abc import ABC, abstractmethod
from typing import Generic, TypeVar, List

T = TypeVar(T)  # Generics для типизации

class IRepository(ABC, Generic[T]):
    """Интерфейс репозитория"""
    
    @abstractmethod
    def get_by_id(self, id: int) -> T | None:
        pass
    
    @abstractmethod
    def get_all(self) -> List[T]:
        pass
    
    @abstractmethod
    def create(self, entity: T) -> T:
        pass
    
    @abstractmethod
    def update(self, id: int, entity: T) -> T | None:
        pass
    
    @abstractmethod
    def delete(self, id: int) -> bool:
        pass

class UserRepository(IRepository[User]):
    """Конкретная реализация для пользователей"""
    
    def __init__(self, session):
        self.session = session
    
    def get_by_id(self, id: int) -> User | None:
        return self.session.query(User).filter(User.id == id).first()
    
    def get_all(self) -> List[User]:
        return self.session.query(User).all()
    
    def create(self, entity: User) -> User:
        self.session.add(entity)
        self.session.commit()
        return entity
    
    def update(self, id: int, entity: User) -> User | None:
        user = self.get_by_id(id)
        if user:
            for key, value in vars(entity).items():
                if not key.startswith(_):
                    setattr(user, key, value)
            self.session.commit()
        return user
    
    def delete(self, id: int) -> bool:
        user = self.get_by_id(id)
        if user:
            self.session.delete(user)
            self.session.commit()
            return True
        return False

class ProductRepository(IRepository[Product]):
    """Другой репозиторий, для продуктов"""
    # Аналогичная реализация для Product
    pass

Использование в бизнес-логике

class UserService:
    """Сервис для работы с пользователями (бизнес-логика)"""
    
    def __init__(self, user_repo: IRepository[User]):
        self.repo = user_repo
    
    def register_user(self, name: str, email: str) -> User:
        """Регистрация нового пользователя"""
        # Проверяем, не существует ли уже пользователь
        existing = self.repo.get_all()  # Или специальный метод
        if any(u.email == email for u in existing):
            raise ValueError("Пользователь с таким email уже существует")
        
        # Создаём пользователя
        user = User(name=name, email=email)
        return self.repo.create(user)
    
    def deactivate_user(self, user_id: int) -> bool:
        """Деактивировать пользователя"""
        return self.repo.delete(user_id)

# Использование
service = UserService(user_repo)
service.register_user("Иван", "ivan@example.com")

Тестирование с Repository Pattern

from unittest.mock import Mock

def test_register_user():
    # Мокируем репозиторий
    mock_repo = Mock(spec=IRepository[User])
    mock_repo.get_all.return_value = []
    mock_repo.create.return_value = User(id=1, name="Иван", email="ivan@example.com")
    
    # Тестируем сервис с мокированным репозиторием
    service = UserService(mock_repo)
    user = service.register_user("Иван", "ivan@example.com")
    
    assert user.id == 1
    assert user.name == "Иван"
    mock_repo.create.assert_called_once()

Преимущества и недостатки

Преимущества:

  • Отделение логики от БД
  • Простое тестирование через моки
  • Единая точка изменения запросов
  • Переиспользование логики
  • Легко менять источник данных

Недостатки:

  • Дополнительный слой абстракции (немного боilerplate кода)
  • Может быть избыточным для простых проектов
  • ORM может уже быть абстракцией

Repository Pattern — это фундаментальный паттерн чистой архитектуры, который делает код более модульным, тестируемым и поддерживаемым.

Что такое паттерн Репозиторий (Repository)? | PrepBro