Чистая архитектура в Python: пошаговая демонстрация. Часть 1

в 22:42, , рубрики: clean code, flask, Pyhon, python, refactoring, tdd, Проектирование и рефакторинг, Совершенный код
Примечание переводчика

Данная статья является переводом. Дословный перевод занял 35 страниц А4 в ворде. Планирую разбить её на 5-6 частей. Думаю, данная тема должна быть полезна многим программистам, желающим писать свои web-приложения лучше и чище. Так же статья полезна тем, кто хочет научиться писать web-приложения с методологией TDD с применением именно модульных тестов, а не интеграционных, как это обычно делалось в тех статьях, что попадались мне на глаза. Если где-то использованы неверные термины или перевод кажется слишком машинным — напишите мне в личку, вряд ли это гугл-транслятор, скорее всего дело в моей косноязычности и посредственном знанием английского языка.

Год назад мой друг Roberto Ciatti познакомил меня с концепцией, которую Роберт Мартин называет чистой архитектурой. Дядя Боб много говорит об этой концепции на конференциях и пишет о ней очень интересные статьи. «Чистая архитектура» представляет собой способ структурирования системы программного обеспечения, набор соглашений о различных слоях и ролях их участников, нечто большее, чем строгие правила.

Как он уже говорил в своей статье «Чистая архитектура» (перевод на хабре), идея самого подхода не нова, она строится на множестве концепций, которые продвигались многими разработчиками программного обеспечения в течение последних 3-х десяти лет. Одну из первых реализаций можно найти в модели Boundary-Control-Entity (Границы-Управление-Сущность), предложенной Ivar Jacobkson в своей шедевральной книге «Инженерия объектно-ориентированного программного обеспечения: Прецеденто ориентированный подход», опубликованной в 1992 году. Но Мартин так же перечисляет другие более свежие версии этой архитектуры.

Я не буду повторять здесь то, что он уже объяснял, (к тому же я не смогу сделать это лучше него), просто размещу ссылки на некоторые ресурсы, которые вы можете поглядеть для ознакомления с этими концепциями:

Цель данной статьи — показать, как создавать web-службы в Python с нуля с использованием чистой архитектуры. Одним из главных преимуществ этого многослойного дизайна является тестируемость, так что я буду программировать, следуя подходу TDD. Проект изначально был разработан с нуля примерно за 3 часа. Учитывая игрушечный характер проекта, некоторые решения были сделаны для упрощения конечного кода. Я укажу на эти упрощения и объясню их, если это покажется необходимым.

Если вы хотите узнать больше о TDD в Python, прочтите статьи из этой категории.

Обзор проекта

Цель проекта «Rent-O-Matic» (фанаты «Дня Щупальца» могут уловить отсыл) состоит в том, чтобы создать простую поисковую систему поверх набора данных объектов, описываемых некоторыми величинами. Эта поисковая система также должна позволять устанавливать фильтры для уточнения поиска.

Записями данных являются складские помещения для аренды, описываемые следующими величинами:

  • Уникальный идентификатор
  • Площадь в квадратных метрах
  • Стоимость аренды в евро/день
  • Координаты (широта и долгота)

Чистая архитектура предполагает разделение различных слоев системы. Сама архитектура описывается четырьмя слоями, которые могут быть реализованы более чем четырьмя фактическими модулями кода. Дам краткое описание этих слоев.

Сущности

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

Важно понимать, что модели в этом слое отличаются от обычных моделей в таких фреймворках как Django. Эти модели не связаны с системой хранения данных, они не могут быть напрямую сохранены или получены с использованием методов этих классов. В них содержатся вспомогательные методы, связанные с бизнес-правилами.

Прецеденты

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

Адаптеры интерфейса

Этот слой соответствует границе между бизнес-логикой и внешними системами и реализует API-интерфейсы, используемые для обмена данными с ними. Система хранения данных, как и пользовательский интерфейс, являются внешними системами для этого слоя, которым необходимо обмениваться данными с прецедентами. И этот слой предоставляет интерфейс для подобного потока данных. В этом проекте связь с представлением обеспечивается через JSON-сериализатор, над которым может быть построен внешний веб-сервис. Адаптер хранилища описывает общий API, с котороым будут работать различные системы хранения.

Внешние интерфейсы

Эта часть архитектуры состоит из внешних систем, которые реализуют интерфейсы, определенные в предыдущем слое. Здесь, к примеру, вы можете найти веб-сервер, который реализует (REST) точки входа, которые имеют доступ к данным (в рамках работы с прецедентами) через JSON-сериализатор. Также здесь находятся реализации систем хранения данных, например, для MongoDB.

API и оттенки

Слово API крайне важно в чистой архитектуре. Каждый слой может быть доступен с помощью API, который представляет собой фиксированный набор точек входа (методов или объектов). Под «фиксированным» подразумевается «одинаков для каждой реализации.» Очевидно, API может меняться со временем. Каждый способ представления будет обращаться к одним и тем же прецедентам и одним и тем же методам для получения доменных моделей, являющихся результатом работы этого конкретного прецедента. Они идут до слоя представления, где эти данные будут форматированы в соответствии с конкретными представлениями информации, например, в HTML, PDF, изображения и т.д. Если вы понимаете архитектуры на основе расширений, то вы уже должны понимать основную концепцию разделённого, API-управляемого компонента (или слоя).

Та же концепция справедлива для слоя хранения. Каждая реализация хранения должна обеспечивать одни и те же методы. Когда имеешь дело с прецедентами, не нужно думать о том, какая система хранения данных используется: ею может быть локальная MongoDB, облачная система хранения или тривиальная таблица в памяти.

Разделение между слоями, как и содержание каждого слоя, не всегда могут оставаться неизменными и зафиксированными. Хорошо спроектированная система должна справляться с такими проблемами, как производительность, или другие специфичные требования. При проектировании архитектуру важно знать «что, где и почему». Особенно это важно знать, когда вы «подгибаете» правила под себя. Не на все вопросы можно ответить однозначно «чёрное» или «белое». Многие решения — это «оттенки серого», то есть, вам самим предстоит определять, что, зачем и куда вы должны поместить.

Но, главное не нарушать структуру чистой архитектуры. В частности, вы должны быть непоколебимы в вопросе потока данных (смотрите раздел «Пересечение границ» в статье Роберта Мартина). Если вы нарушите поток данных, то сломаете всю структуру. Позвольте мне подчеркнуть еще раз: никогда не нарушайте поток данных. Примером нарушения потока данных будет выдача Python-класса каким-либо прецедентом вместо представления этого класса (к примеру, в виде JSON строки).

Структура проекта

Давайте посмотрим на структуру проекта.

Для построения структуры проекта был использован Cookiecutter. Бегло пройдусь по этой части. Каталог rentomatic содержит следующие подкаталоги:

  • domain
  • repositories
  • REST
  • serializers use_cases

Эти каталоги отражают слоистую структуру, представленную в предыдущем разделе, а также структуру каталога с тестами, созданного так, чтобы тесты можно было легко найти.

Исходный код

Вы можете найти исходный код в этом репозитории GitHub. Делайте форки от него, экспериментируйте, изменяйте его, ищите более удачные пути решения проблемы, которую я обсуждаю в этой статье. Исходный код содержит коммиты с тэгами, что позволит вам проследить за процессом создания так, как это описывается в статье. В самой статье под заголовками размещены лэйблы <Git tag: название тега>. Лэйбл — это просто ссылка на коммит с тэгом на GitHub, по которой можно перейти на гитхаб, чтобы посмотреть код без его клонирования.

Инициализация проекта

Git tag: Step01

Обновление: этот пакет Cookiecutter создает окружение точно такое же, как в этом разделе. Следующее описание объясняет, как управлять зависимостями и конфигурациями, приглядитесь к этому инструменту, может быть вы используете его в своём следующем проекте.

Обычно я люблю разворачивать виртуальную среду Python внутри проекта, так что, я создам временное виртуальное окружение для установки cookiecutter, создам проект, и удалю virtualenv. Cookiecutter задаст вам несколько вопросов о Вас и о проекте, чтобы предоставить первоначальную структуру файлов. Мы собираемся создать нашу собственную среду для тестирования, так что лучше ответить, «no» на use_pytest. Это демонстрационный проект, в котором нам нет необходимости в публикации, так что можно ответить «no» и на use_pypi_deployment_with_travis. Проект не имеет интерфейса командной строки, и можно спокойно создать файл автора и использовать любую лицензию.

virtualenv venv3 -p python3
source venv3/bin/activate
pip install cookiecutter
cookiecutter https://github.com/audreyr/cookiecutter-pypackage

Теперь ответьте на вопросы, а затем завершите создание проекта с помощью следующего кода

deactivate
rm -fR venv3
cd rentomatic
virtualenv venv3 -p python3
source venv3/bin/activate

Избавьтесь от файла requirements_dev.txt, который создал для вас Cookiecutter. Я, обычно, храню зависимости virtualenv в различных иерархических файлах для разделения продакшн, девелоп и тестовой среды, так что, давайте создадим каталог requirements и соответствующие файлы:

mkdir requirements
touch requirements/prod.txt
touch requirements/dev.txt
touch requirements/test.txt

Файл test.txt будет содержать конкретные пакеты, используемые для тестирования проекта. Так как для тестирования проекта необходимо установить пакеты для девелоп-среды, в файл необходимо включить девелоп-зависимости.

-r prod.txt

pytest
tox
coverage
pytest-cov

Файл dev.txt будет содержать пакеты, используемые в процессе разработки, а так же устанавливать пакеты для тестирования и продакшена.

-r test.txt

pip
wheel
flake8
Sphinx

(пользуясь тем, что test.txt уже включает в себя prod.txt).

Последний, основной файл requirements.txt будет просто импортировать requirements/prod.txt

-r prod.txt

Очевидно, что вы можете найти структуру проекта, которая лучше подходит для ваших потребностей или предпочтений. Эта структура подходит для данного проекта, но я не заставляю использовать её для всех ваших следующих проектов.

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

Как видно, я не использую версии тегов в файлах зависимостей. Это потому, что этот проект не будет запускаться на продакшене, так что окружение можно не замораживать.

Не забудьте установить девелоп-зависимости внутри virtualenv

$ pip install -r requirements/dev.txt

Различные конфигурации

Библиотека тестирования pytest должна быть настроена. Это делается через файл pytest.ini, который вы можете создать в корневом каталоге (где расположен файл setup.py)

[pytest]
minversion = 2.0
norecursedirs = .git .tox venv* requirements*
python_files = test*.py

Для запуска тестов во время разработки проекта просто выполните

$ py.test -sv

Если вы хотите проверить покрытие, т.е. объем кода, который запускается с помощью тестов, выполните

$ py.test --cov-report term-missing --cov=rentomatic

Если вы хотите узнать больше о покрытии тестами, поглядите на официальную документацию пакетов Coverage.py и pytest-cov.

Я настоятельно рекомендую использовать пакет flake8, чтобы проверить ваш Python-код на PEP8 совместимость. Вот настройки flake8, которые необходимо разместить в файле setup.cfg:

[flake8]
ignore = D203
exclude = .git, venv*, docs
max-complexity = 10

Для проверки соответствия кода стандарту PEP8, выполните

$ flake8

Документация по Flake8 доступна здесь.

Обратите внимание, что каждый шаг на этом посту создаёт тестирование кода и покрытие в 100%. Одним из преимуществ чистой архитектуры является разделение между слоями, что гарантирует большую степень тестируемости. Однако следует отметить, что в этом учебном пособии, в частности, в секциях REST, некоторые тесты будут опущены в пользу более простого описания архитектуры.

Продолжение следует...

Автор: CJay

Источник

Поделиться