Год назад мой друг Roberto Ciatti познакомил меня с концепцией, которую Роберт Мартин называет чистой архитектурой. Дядя Боб много говорит об этой концепции на конференциях и пишет о ней очень интересные статьи. «Чистая архитектура» представляет собой способ структурирования системы программного обеспечения, набор соглашений о различных слоях и ролях их участников, нечто большее, чем строгие правила.
Как он уже говорил в своей статье «Чистая архитектура» (перевод на хабре), идея самого подхода не нова, она строится на множестве концепций, которые продвигались многими разработчиками программного обеспечения в течение последних 3-х десяти лет. Одну из первых реализаций можно найти в модели Boundary-Control-Entity (Границы-Управление-Сущность), предложенной Ivar Jacobkson в своей шедевральной книге «Инженерия объектно-ориентированного программного обеспечения: Прецеденто ориентированный подход», опубликованной в 1992 году. Но Мартин так же перечисляет другие более свежие версии этой архитектуры.
Я не буду повторять здесь то, что он уже объяснял, (к тому же я не смогу сделать это лучше него), просто размещу ссылки на некоторые ресурсы, которые вы можете поглядеть для ознакомления с этими концепциями:
- «Чистая архитектура» — пост Роберта Мартина, который лаконично описывает цели архитектуры. В нем также перечислены ресурсы со схожими архитектурами.
- Принцип открытости-закрытости — статья Роберта Мартина, не сильно связанная с концепцией Чистой архитектуры, но важная для понимания концепции разделения.
- Hakka Labs: Роберт «Дядя Боб» Мартин — Архитектура: потерянные годы видео Роберта Мартина из Hakka Labs.
- DDD и Стратегии тестирования Лаури Taimila
- (скоро выйдет) книга «Чистая Архитектура» Роберта Мартина, опубликованная Prentice Hall.
Цель данной статьи — показать, как создавать 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