Объяснение шаблона проектирования репозитория

Раньше я работал с такими фреймворками, как SpringBoot, но я никогда не задумывался над причинами, лежащими в основе большинства конструкций. Только когда мне нужно было разработать API с нуля без использования каких-либо фреймворков, я понял ценность шаблонов проектирования, встроенных в фреймворки.

В этой статье мы обсудим шаблон репозитория. Репозиторий — это уровень абстракции поверх кода, который взаимодействует с уровнем данных (например, файловой системой, базой данных).

На практике репозиторий обычно состоит из класса с методами для выполнения операций CRUD в базе данных.

Например, предположим, что мы создали репозиторий для выполнения запросов к базе данных с помощью библиотеки Python SQLAlchemy.

class SqlAlchemyOrdersRepository:
    def __init__(self, session):
        self._session = session

    def add(self, order: Order):
        self._session.add(order)

    def find(self, **kwargs):
        return self._session.query(Order).filter_by(**kwargs).first()

    def find_all(self, **kwargs):
        return self._session.query(Order).filter_by(**kwargs).all()

    def delete(self, order: Order):
        self._session.delete(order)

Преимущество использования этой абстракции в остальной части нашей кодовой базы, а не выполнения функций непосредственно с использованием экземпляра Session, заключается в том, что мы можем легко заменить его чем-то другим, что, в свою очередь, упрощает тестирование кода.

Предположим, мы определили следующий сервис:

class MyService:
 self._repository = None

 def __init__(repository: SqlAlchemyOrdersRepository):
  self._repository = repository

 def add(order: Order):
  ...
  self._repository.add(order)
  ...

Затем мы можем создать абстрактный класс с теми же методами, что и SqlAlchemyOrdersRepository.

class AbstractOrdersRepository(abc.ABC):
    @abc.abstractmethod
    def find(self, **kwargs):
        raise NotImplementedError

    @abc.abstractmethod
    def add(self, order: Order):
        raise NotImplementedError

    @abc.abstractmethod
    def delete(self, order: Order):
        raise NotImplementedError

    @abc.abstractmethod
    def find_all(self, **kwargs):
        raise NotImplementedError

Мы делаем SqlAlchemyOrdersRepository наследником AbstractOrdersRepository.

class SqlAlchemyOrdersRepository(AbstractOrdersRepository):
 ...

Затем мы заменяем все ссылки на SqlAlchemyOrdersRepository на AbstractOrdersRepository.

class MyService:
 self._repository = None

 def __init__(repository: AbstractOrdersRepository):
  self._repository = repository

 def add(order: Order):
  ...
  self._repository.add(order)
  ...

Наконец, определяем подделку. Фальшивые объекты, в отличие от макетов, на самом деле имеют рабочие реализации, но обычно используют некоторые сокращения, которые делают их непригодными для производства (например, в базе данных памяти).

class FakeOrdersRepository(AbstractOrdersRepository):
    def __init__(self, orders: List[Order]):
        self._orders = set(orders)

    def find(self, **kwargs):
        for order in self._orders:
            if all(getattr(order, k) == v for k, v in kwargs.items()):
                return order
        return None

    def find_all(self, **kwargs):
        return [
            order
            for order in self._orders
            if all(getattr(order, k) == v for k, v in kwargs.items())
        ]

    def add(self, order: Order):
        self._orders.add(order)

    def delete(self, order: Order):
        self._orders.remove(order)

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

class TestMyService:

 def test_add():
  repository = FakeRepostiory([])
  order = Order('id')
  my_service = MyService()
  my_service.add(order)

  assert repository._orders == [order]
  assert ...

Заворачивать

Репозиторий — это слой абстракции поверх кода, который взаимодействует со слоем данных. Репозиторий можно «подделать», чтобы тестировать код независимо от базы данных.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу