Создаем приложение: Docker, VueJs и Python-Sanic. Часть 2

В предыдущей статье мы рассмотрели, как быстро поднять Docker окружение для разработки, используя возможности docker-compose. В этой статье окунемся в разработку backend-а и «контейнеризируем» API, написанное на Python.

Итак, уточним задачу, которую предстоит реализовать в API:

  1. Пусть новое приложение по ссылке /api/v1.0/user/auth принимает POST запрос с параметрами username и password и, в случае совпадения данных доступа с учетной записью в таблице users, возвращает token идентификации.
  2. Данные об авторизованном пользователе (users.id) пишем в Redis, где ключом (key) будет token, созданный в пункте 1.

Этап 1. Небольшая автоматизация рутины

Во время разработки я довольно часто пользуюсь возможностями утилиты make, позволяющей намного сократить «вербальность» некоторых команд. Для этого в корне проекта создаю Makefile с набором удобных инструкций-сокращений. Приведу список наиболее полезных для меня:

make up — просто запускает, а make upb — еще и полностью перестраивает перед запуском все существующие контейнеры.

make stop — останавливает все работающие контейнеры.

make db — подключаемся к БД PostgreSQL при помощи консольного клиента pgSQL. Обратите внимание, что мы в первой строчке Makefile «заинклюдили» все переменные окружения, которые у нас есть в .env файле. Поэтому здесь могут быть довольно экзотические инструкции, список которых ограничен лишь фантазией разработчика. К примеру, у меня ещё здесь находятся инструкции по деплою кода на продакшен при помощи ansible).

make r — быстрый способ посмотреть значения в базе данных Redis через консольную утилиту redis-cli.

make test — запускает unit/integrity tests, которые нам еще предстоит написать для нашего /api.

Последняя значимая make-инструкция — это (пример) make b c=test_api — подключиться к bash-консоли любого контейнера с явно указанным именем.

Этап 2. Создаем минимально рабочее API

Итак:

Перед тем как мы начнем реализовать логику API, нам необходимо сделать минимально работающее Sanic-приложение, контейнеризировать его и настроить проксирование всех запросов, начинающихся на /api в контейнере с nginx на наш новый контейнер. Для этого «утащим» пример hello world отсюда и слегка модифицируем его. В каталоге api нашего проекта, создаем файл run.py со следующим содержимым:

Теперь нам нужно запустить run.py внутри контейнера. Для этого создаем api/Dockerfile со следующим содержимым:

В этом файле мы указываем компоновщику, что для создания нашего контейнера нужно взять последний Docker образ Python версии 3.6.7. Далее создаем рабочий каталог /app для нашего микросервиса.

Отступление. Я взял за правило помещать весь рабочий код приложения в корневую папку /app контейнера. Аналогично будет и в примере с клиентским app, которое будет реализовано в следующей части.

Далее идет установка дополнительного пакета gcc, без которого не получится инсталлировать некоторые pip-пакеты из api/requirements.txt, которые мы указали чуть выше, при его создании. Далее ничего особенного, перейдем к настройке файла docker-compose.yml.

Этап 3. Добавляем api в docker-compose.yml

Здесь в 4-й строке мы «заставляем» docker-compose изменить контекст и пройти по указанному адресу для чтения инструкций, созданному нами во 2-м этапе «Dockerfile», который создает контейнер с работающим приложением. Особо важной для нас, как для разработчиков, является директива tty, которая позволяет подключаться (docker attach) к консоли вывода работающего процесса в контейнере. Эта жизненно необходимая для dev разработки инструкция позволит нам «дебажить» и «тюнинговать» наше приложение.

Кроме того, мы указали что наш сервис «связан» (links) c контейнерами db и redis через подсеть (network) internal.

В строчке 7 мы «пробрасываем» папку api «внутрь» контейнера (механизм volumes).

В строчке 14, как я уже рассказывал в первой части, мы пробрасываем и делаем доступными для использования приложением переменных окружения из файла .env, созданного в прошлой части. Одна из этих переменных (API_MODE), уже используется в run.py.

Этап 4. Изменяем конфигурацию nginx

Всё, что нам осталось сделать, — добавить правила проксирования для сервиса nginx. Для этого отредактируем файл nginx/server.conf, добавив:

Теперь пересоздадим контейнеры с учетом новых изменений.

Этап 5. Остановка и перестройка контейнеров

С учетом вышеописанного в статье, у нас всё готово для запуска:

В браузере вбиваем http://localhost/api, результатом должно быть {"hello":"world«}

Этап 6. Как этим пользоваться

Благодаря переменной окружения API_MODE=dev из файла .env, мы запустили приложение в режиме debug. Фреймворк sanic, как и множество других Python Web-фреймворков, поддерживает hot-reload. То есть сервер приложения автоматически будет перезапускаться, как только мы изменим один из файлов, связанных с run.py. Проверить это довольно легко: исправьте «world» на «world!» в коде run.py и тут же обновите страницу браузера. Вы увидите, что информация обновилась.

Но перезагрузки кода не достаточно. Нам необходимо «дебажить» код и где-то видеть логи приложения. Для этого существуют две команды:

Первая из них подключается к консольному выводу работающего процесса (чтобы отключиться Ctrl+C), куда мы, при помощи стандартного пакета logging (или просто print), можем выводить любую debug-информацию. Вторая команда необходима для тех случаев, когда мы ловим fatal, «не совместимый с жизнью самого процесса». Вывод этой команды показывает нам backtrace, предшествовавший ошибке.

На заметку. Я выше обращал внимание на инструкцию tty=true. Как раз она позволяет делать «безболезненный» detach по Ctrl+C, не уничтожая сам процесс.

Этап 7. Авторское отступление

Лично я, когда программирую backend, то консоль с выводом (docker attach test_api) у меня открыта в терминальном окне редактора кода. Еще, для поддержки технологии intellisense, которая есть во многих популярных редакторах, я внутри папки api создаю виртуальное окружение .venv, в которое устанавливаю те же пакеты из requirements.txt, что установлены в самом контейнере:

В корневой директории в файле .gitignore указана инструкция пропуска всех папок и файлов, начинающихся с «.», поэтому можно не переживать, что «мусор» попадет в репозиторий.

Этап 8. Реализация логики

В рамках статьи невозможно делать полный копипаст кода, который я написал (много файлов и большой объем), поэтому, как говорил в прошлой статье, будет ссылка на pull request, где можно посмотреть изменения, которые были детально описаны выше. Прокомментирую наиболее важные моменты.

Был добавлен файл api/application.py, содержащий функцию-фабрику, которую будем использовать в 2-х местах: в run.py для сервера приложения и в conftest.py — для создания экземпляра (fixture) приложения, для написания интеграционного теста. Хотел бы обратить особое внимание на функцию db_migrate. Фактически она делает то же самое, что и расширение alembic, которое помогает делать миграцию схемы БД между различными ветками кода. Я реализовал схему миграции следующим образом:

Немного пояснений. В момент перезапуска сервера система «смотрит» в БД в поисках значения таблицы version. Если такой таблицы нет, текущая версия определяется как «0» и приложение автоматически запускает все скрипты из ветки «up» до тех пор, пока значение ключа из SCHEMA не станет равным LATEST_VERSION. Последняя успешная версия фиксируется в version. Обратный процесс аналогичен. Изменяя значение LATEST_VERSION, функция миграции «спускается» вниз на любую версию. При этом она запускает все скрипты из «down», вплоть до полной очистки базы данных, при условии, если LATEST_VERSION указываем равным «0».

Еще хотелось бы обратить внимание на то, что доступ к версиям api реализован при помощи механизма blueprint, который выбран из-за возможной версионности api в будущем.

На заметку. Когда пишем интеграционные тесты, то тестируем URL локального сервера, то есть тестируем /v1.0/user/auth. Но когда в следующей статье перейдем к реализации frontend, мы будем обращаться к «api» через прокси-сервер nginx в формате /api/v1.0/user/auth.

Этап 9. Запуск

Следующая статья будет завершающей. В ней напишем WebSocket сервер fronted на VueJs и заставим всё это работать вместе.

Похожие статьи:
Генеральний директор OpenAI Сем Альтман дав розлоге інтервʼю, в якому пригадав конфлікт з правлінням у 2023 році, розповів про свій робочий...
20 жовтня Кабмін затвердив проєкт закону «Про внесення змін до деяких законів України щодо розвитку індивідуальних освітніх...
Сьогодні Кабмін звільнив голову Державної служби спеціального зв’язку та захисту інформації Юрія Щиголя та його...
В выпуске: .NET Container Images, Rest vs. GraphQL, почему шардинг — это непросто, коференция .NET Fest 2018. .NET Dissecting new generic constraints in C#...
Оператор Tele2 в октябре 2015 года запустил в коммерческую эксплуатацию сети скоростного мобильного интернета в...
Яндекс.Метрика