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

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

Год назад мой друг 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» (фанаты «Дня Щупальца» могут уловить отсыл) состоит в том, чтобы создать простую поисковую систему поверх набора данных объектов, которые описываются некоторыми величинами. Эта поисковая система должна позволять устанавливать некоторые фильтры для сужения область поиска.


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


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

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


Сущности


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


Очень важно понимать, что модели в этом слое отличаются от обычных моделей в таких фреймворках как 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. Не стесняйтесь делать форки от него и экспериментировать, менять его, и найти более удачные пути решения проблемы, которую я буду обсуждать в этой статье. Исходный код содержит коммиты с тэгами, что позволит вам проследить за процессом создания так, как это описывается в статье. Вы можете найти текущий тег под лейблом название тега> в разделе с заголовками. Лэйбл — это просто ссылка на коммит с тэгом на 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, некоторые тесты были опущены в пользу более простого описания архитектуры.


продолжение следует…

Комментарии (4)

  • 9 января 2017 в 03:45

    0

    Прочитал только первую предложением и сразу (до прочтением остальное) захочу поделился неизгладимый впечатление о переводом!
    • 9 января 2017 в 09:57

      0

      Как я вас понимаю)))
  • 9 января 2017 в 09:50

    0

    Автор. Будьте добры, подкорректируйте перевод, во имя великого и могучего русского языка и аудитории хабрахабра. Мы ведь не находимся на одном уровне развития с персонажами фильма «Идиократия». Глаза и слух действительно сильно режет.
    • 9 января 2017 в 09:56

      0

      Понял. Обязательно исправлю.

© Habrahabr.ru