Репозиторий(Repository)

Репозиторий(Repository)

KarmanovichDev

Первая часть - https://telegra.ph/RepozitorijRepository-03-25

Давайте немного глубже разберём, что такое репозиторий и какие обязанности на нём лежат.

Начну с того, какие конкретно у меня ожидания от репозитория:

  1. Один репозиторий, одна бизнес-сущность. Исключением может быть, если вы пишете придерживаясь архитектуры DDD и тогда одна корневая сущность может содержать другие(например корзина пользователя, содержащая товары).
  2. Репозиторий является централизованной точкой доступа к данным. Только он должен обладать знаниями о хранении требуемых данных.
  3. Репозиторий должен поддерживать транзакционную целостность. Если, чтобы сохранить ваш объект, требуется поочерёдно записать данные в три разных таблицы, то до момента сохранения данных в третью, никто не должен знать об изменениях в двух первых(ACID). Также при ошибке записи в третью таблицу все остальные изменения должны быть откачены, и база данных должна быть возвращена в изначальное состояние(ACID). Если вдруг ваш репозиторий использует API другого сервиса, и сохранение данных происходит через отправку данных на несколько URL, то ваш репозиторий должен уметь откатить и такие изменения при неудачной попытке сохранения.


Как же наличие репозитория помогает нам тестировать остальной код? Вспомним код из первой части без репозитория.

Тестировать такой код можно либо интеграционными тестами(минусы рассказывал в первой половине статьи), либо большим количеством патчей ORM(не самые выразительные тесты).


А теперь давайте вспомним код, который содержит репозиторий:

Сам репозиторий всё так же придётся тестировать либо интеграционным тестом, или с использованием патчей. Но эти тесты будут изолированы от остальной логики.

Что же до GetAllPricesUseCase, то теперь мы можем написать модульные тесты. И для этого мы сделаем подставную реализацию репозитория, которая будет внедряться при инициализации GetAllPricesUseCase.

Как видите, наш подставной репозиторий отлично справился со своей задачей. Мы написали изолированный от инфраструктуры юнит-тест.


А как же тестировать сам репозиторий? Тут всё просто, варианта два, оба имеют свои преимущества и недостатки в различных ситуациях.

  1. Интеграционные тесты с использованием реальной базы. Если в CI у вас тесты запускаются на такой же БД как и в production окружении(а так и должно быть), то локально можно вполне писать тесты на sqlite(пока это не доставляет проблем).
  2. Модульные тесты. Сложность зависит от ORM, например, на django сложнее писать такие тесты, потому что нужно патчить модели и их методы. Но, например, на sqlalchemy можно проектировать репозитории так, чтобы при инициализации они принимали снаружи объект сессии, которая, в свою очередь, будет Mock или FakeSession сессией с определённой логикой работы(но которая не использует базу данных).


Как я уже говорил ранее, репозиторий не всегда равно база данных, и вот два примера, которые продемонстрируют это:

  1. API репозиторий(укороченный вариант, чтобы показать суть)
Пытливые умы могут возразить мутации post.id в методе save. Но это всего лишь компромисс при написании ПО их на вашем пути будет немало :)

2. Так называемый InMemoryRepository, когда все данные хранятся в оперативной памяти(в состоянии класса).

В целом данные могут лежать где угодно и в каком угодно виде. Ведь суть репозитория - абстракция над уровнем хранения данных.

Report Page