Git tag: Step09
 Наша реализация ответов и запросов, наконец, завершена. И теперь мы можем реализовать последнюю версию нашего сценария. Сценарий корректно возвращает объект ResponseSuccess, но до сих пор не проверяет корректность входящего запроса.
 Давайте изменим тест в файле tests/use_cases/test_storageroom_list_use_case.py и добавим ещё 2 теста. Полученный набор тестов (после фикстуры domain_storagerooms) выглядит следующим образом:
 Этот тест отличается от предыдущего тем, что теперь метод assert_called_with() вызывается с параметром filters=None. В строках с импортом появились response_objects и request_objects. Фикстура Domain_storagerooms была исключена из кода для лаконичности.
 Этот тест проверяет, что при вызове хранилища используется значение ключа filters, который используется и для создания запросов.
 Эти последние два теста проверяют поведение сценария при возникновении исключения в хранилище или при некорректном запросе.
 Изменим файл rentomatic/use_cases/storageroom_use_cases.py так, чтобы он содержал новую реализацию сценария, позволяющую тестам проходить успешно.
 Как видите, метод execute() проверяет корректность запроса, в противном случае возвращает ResponseFailure, созданный на основе того же объекта запроса. Вот теперь бизнес-логика реализована, вызывая хранилище и возвращая успешный ответ. Если же в этой фазе что-то пойдет не так, перехватывается исключение и возвращается сформированный нужным образом ResponseFailure.
 Git tag: Step10
 Чистая архитектура — это не фреймворк. Не стоит ждать от него столько же, сколько от таких продуктов, как Django, который предоставляет модели, ORM, всевозможные структуры и библиотеки. Тем не менее, некоторые классы могут быть выделены из нашего кода и представлены в виде библиотеки, так, чтобы можно было повторно использовать этот код. В этом разделе я познакомлю вас с рефакторингом уже существующего кода, при помощи которого мы изолирует основные фичи для запросов, ответов и сценариев.
 Мы уже выделили объект ответа. Так как test_valid_request_object_cannot_be_used проверяет общее поведение, а не связь между моделью StorageRoom и сценариями, то мы можем переместить его из tests/use_cases/test_storageroom_list_request_objects.py в tests/shared/test_response_object.py.
 Затем мы можем переместить классы InvalidRequestObject и ValidRequestObject из rentomatic/use_cases/request_objects.py в rentomatic/shared/request_object.py, внося необходимые изменения в классе StorageRoomListRequestObject, который теперь наследуется от внешнего класса.
 А вот класс сценария подвергается значительным изменениям. Класс UseCase тестируем кодом файла tests/shared/test_use_case.py:
 Этот тест проверяет, что класс UseCase нельзя использовать для обработки входящих запросов.
 Тест выполняет сценарий с некорректным запросом и проверяет ответ. Так как запрос неправильный, типом ответа является PARAMETERS_ERROR, тем самым говоря о наличии проблемы в параметрах запроса.
 Этот тест заставляет сценарий вызывать исключение. Этот тип ошибки классифицируется как SYSTEM_ERROR, который представляет собой общее название для исключения, которое не связано с параметрами запроса или фактическими сущностями.
 Как видно из последнего теста, идея состоит в том, чтобы предоставить метод execute() класса UseCase и вызвать метод process_request(), который определён в каждом дочернем классе, являющимся нашим сценарием.
 Файл rentomatic/shared/use_case.py содержит следующий код для успешного прохождения теста:
 И вот теперь файл rentomatic/use_cases/storageroom_use_cases.py содержит следующий код:
 Git tag: Step11
 Слой хранилища — это тот слой, в котором имеется система хранения данных. Как вы видели, когда мы реализовали сценарий, мы получали доступ к хранилищу данных через API, в данном случае через метод хранилища list(). Уровень абстракции, предоставляемой уровнем хранилища, выше, чем уровень, предоставляемый ORM или таким инструментом, как SQLAlchemy. Слой хранилища обеспечивает только необходимые для приложения конечные точки с интерфейсом, который адаптирован для конкретных бизнес-задач и целей приложения.
 Для ясности, говоря в терминах конкретных технологий, SQLAlchemy является прекрасным инструментом для абстрактного доступа к базе данных SQL. Внутренняя реализация хранилища слоя может использовать его для доступа к базе данных PostgreSQL. Но внешний API слоя — не то же самое, что даёт SQLAlchemy. API — это (как правило, уменьшенное) множество функций, которые вызывают сценарии для получения данных. В самом деле внутренняя реализация API может использовать сырые запросы SQL. Хранилище даже может не быть основанным на базе данных. Мы можем иметь слой хранилища, который извлекает данные из службы REST или делает удаленные вызовы процедур через RabbitMQ.
 Очень важной особенностью слоя хранилища является то, что он всегда возвращает модели предметной области (как и ORM-фреймворки).
 В этой статье я не буду разворачивать реальную базу данных. Возможно, я напишу это в друших статьях, в которых будет выделено достаточно места для реализации двух различных решений, и в которых получится показать, как API хранилища может скрыть фактическую реализацию.
 Вместо этого я собираюсь создать очень простую систему хранения в памяти с некоторыми заранее заданными данными. Думаю, для демонстрации концепции хранилища этого будет достаточно.
 Первое, что нужно сделать, это написать несколько тестов, которые описывают публичный API хранилища. Файл tests/repository/test_memrepo.py содержит тесты для данной задачи.
 Сначала мы добавим данные, которые будем использовать в тестах. Импортируем доменную модель для проверки корректного типа результата вызова API:
 Поскольку объект хранилища будет возвращать модели предметной области, нам нужна вспомогательная функция для проверки правильности результатов. Следующая функция проверяет длину двух списков и гарантирует, что все возвращенные элементы модели из предметной области, и сравнивает коды. Обратите внимание, что мы можем использовать встроенную функцию isinstance(), поскольку DomainModel является абстрактным базовым классом и наши модели зарегистрированы (см rentomatic/domian/storagerooms.py)
 Нам нужно иметь возможность инициализировать хранилище со списком словарей, и чтобы вызов метода list() без какого-либо параметра возвращал тот же список записей.
 Метод list() должен принимать параметр filters, являющийся словарём. Ключи словаря должны быть в виде <attribute>__<operator>, похожий на синтаксис, используемый в Django ORM. Таким образом, чтобы выразить, что цена должна быть меньше 65, можно записать filters={'price__lt': 60}.
 Нужно проверить несколько условий возникновения ошибки: неизвестный ключ должен вызвать исключение KeyError, а использование неверного оператора должно вызвать исключение ValueError.
 Давайте проверим, что механизм фильтрации работает. Мы хотим, чтобы оператором по умолчанию был __eq. Это означает, что если мы не передаём какой-либо оператор, должен выполняться оператор равенства.
 Реализация класса MemRepo довольно проста, и я не буду объяснять её построчно.