Репозиторий(Repository)
KarmanovichDevПервая часть - https://telegra.ph/RepozitorijRepository-03-25
Давайте немного глубже разберём, что такое репозиторий и какие обязанности на нём лежат.
Начну с того, какие конкретно у меня ожидания от репозитория:
- Один репозиторий, одна бизнес-сущность. Исключением может быть, если вы пишете придерживаясь архитектуры DDD и тогда одна корневая сущность может содержать другие(например корзина пользователя, содержащая товары).
- Репозиторий является централизованной точкой доступа к данным. Только он должен обладать знаниями о хранении требуемых данных.
- Репозиторий должен поддерживать транзакционную целостность. Если, чтобы сохранить ваш объект, требуется поочерёдно записать данные в три разных таблицы, то до момента сохранения данных в третью, никто не должен знать об изменениях в двух первых(ACID). Также при ошибке записи в третью таблицу все остальные изменения должны быть откачены, и база данных должна быть возвращена в изначальное состояние(ACID). Если вдруг ваш репозиторий использует API другого сервиса, и сохранение данных происходит через отправку данных на несколько URL, то ваш репозиторий должен уметь откатить и такие изменения при неудачной попытке сохранения.
Как же наличие репозитория помогает нам тестировать остальной код? Вспомним код из первой части без репозитория.

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

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

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

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

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