Вернуться к статьям

Практический гид: внедрение архитектурного паттерна Repository на примере Python + SQLAlchemy

Программирование 14.02.2026 30 просмотров

Ключевые слова

repository pattern репозиторий архитектурные паттерны python sqlalchemy тестирование разделение слоёв модульность
Практический гид: внедрение архитектурного паттерна Repository на примере Python + SQLAlchemy

Современная разработка требует не только написания кода, который «работает», но и такого, который легко тестировать и сопровождать. Архитектурные паттерны призваны снизить связанность и повысить самостоятельность компонентов. Один из таких паттернов — Repository. В этой статье разберём, что это такое, зачем внедрять Repository в проектах на Python (например, с ORM SQLAlchemy), и как реализовать простую структуру шаг за шагом с примерами кода.

Зачем нужен паттерн Repository?

Проблема монолитного доступа к данным: Часто бизнес-логика напрямую работает с ORM-моделями или даже с «сырыми» SQL-запросами. Это усложняет тестирование, делает код зависимым от базы данных, усложняет миграции и переход с одной технологии хранения к другой.

  • Бизнес-логика переплетается с запросами к базе.
  • Трудно подменить хранилище (например, с PostgreSQL на Redis).
  • Единичные тесты становятся сложнее: приходится поднимать БД или усложнять моки.

Паттерн Repository решает это, выступая в роли «прослойки» между бизнес-логикой и слоем данных. Ваш код работает с абстрактным интерфейсом, не зная, как и где хранятся данные.

Базовая структура паттерна Repository: теория и простая реализация

1. Схема: кто с кем взаимодействует

  • Модели данных: например, SQLAlchemy ORM.
  • Repository: класс с методами для работы с моделями (создать, получить, отфильтровать, удалить и пр.).
  • Бизнес-логика: модули, использующие Repository, не зная деталей хранения.

2. Минимальный пример Python + SQLAlchemy

from sqlalchemy.orm import Session
from models import User  # SQLAlchemy-модель

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

    def get_by_id(self, user_id: int) -> User | None:
        return self.session.query(User).filter(User.id == user_id).first()

    def add(self, user: User) -> None:
        self.session.add(user)

    def delete(self, user: User) -> None:
        self.session.delete(user)

Бизнес-логике («сервисному слою») достаточно передать экземпляр этого репозитория. Фреймворк (например, FastAPI) прокидывает сессию, а сервис работает только с интерфейсом UserRepository.

3. Пример использования репозитория в бизнес-логике

def create_user(repo: UserRepository, name: str) -> User:
    user = User(name=name)
    repo.add(user)
    return user

Теперь create_user можно протестировать, подменив UserRepository на «фейковую» реализацию (Fake/Mock).

Внедрение Repository на практике: рекомендации, тестирование и расширение

1. Описываем интерфейс

from typing import Protocol

class IUserRepository(Protocol):
    def get_by_id(self, user_id: int): ...
    def add(self, user): ...
    def delete(self, user): ...

В Python (с 3.8) используем Protocol для описания интерфейса без необходимости строгой реализации.

2. Тестирование бизнес-логики без базы данных: мок-репозиторий

class FakeUserRepository:
    def __init__(self):
        self.users = {}
    def get_by_id(self, user_id):
        return self.users.get(user_id)
    def add(self, user):
        self.users[user.id] = user
    def delete(self, user):
        del self.users[user.id]

Теперь юнит-тесты работают быстро, не требуют настоящей базы:

def test_create_user():
    repo = FakeUserRepository()
    user = User(id=1, name='Тестовый')
    repo.add(user)
    assert repo.get_by_id(1) == user

3. Расширение: универсальный BaseRepository и специфичные методы

  • Единообразие методов: вынести общие вещи в абстрактный класс или Generic.
  • Специализация: кастомные методы только для бизнес-логики вашего приложения (например, find_by_email).
from typing import TypeVar, Generic, Type

T = TypeVar('T')

class BaseRepository(Generic[T]):
    def __init__(self, model: Type[T], session: Session):
        self.model = model
        self.session = session
    def get_by_id(self, obj_id):
        return self.session.get(self.model, obj_id)

А далее создаём UserRepository(BaseRepository[User]) и добавляем уникальные методы к нему.

Преимущества и ключевые советы

  • Тестируемость: бизнес-логика не зависит от БД, тесты изолированы и быстры.
  • Гибкость внедрения: переход с одной системы хранения на другую облегчён.
  • Ясная структура приложения: легче масштабировать и сопровождать кодовую базу.
  • Можно применять на проектах любого масштаба — от маленьких pet-проектов до крупных систем.

Рекомендация: всегда внедряйте интерфейс/абстракцию и конкретную реализацию для настоящей базы и для тестов.

Заключение: стоит ли внедрять Repository в свой проект?

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

Попробуйте внедрить этот подход на небольшом сервисе: вы удивитесь, насколько чище и надёжнее станет ваша кодовая база!