На Хабре я часто вижу статьи о реализации тех или иных фич на Python-фреймворках. Я объединил все эти фичи в реальный проект с открытым исходным кодом, чтобы у вас сложилась целостная картина. Мы с вами создадим UX/UI на Figma, напишем фронтенд на HTML, CSS, SASS, Bootstrap и JavaScript, создадим ER-диаграмму в MySQL Workbench, напишем бекэнд на Flask, создадим регистрацию через социальные сети OAuth 2.0 в один клик, используем брокер сообщений и асинхронную очередь Celery для отправки писем на электронную почту, сделаем WYSIWYG-редактор, реализуем полнотекстовый поиск Elasticsearch, закешируем Redis, покроем тестами pytest и запустим в Docker-контейнерах, поговорим о многопроцессности для WSGI-шлюза Gunicorn.

План работы и технологии

Здесь я приведу последовательность шагов, которые мы будем последовательно выполнять:

Номер шага

Что делаем

Технология

1

Изучаем техническое задание

2

Создаём дизайн веб-приложения UX/UI

Figma

3

Пишем HTML версию веб-приложения, подключаем CSS и JavaScript, делаем адаптивную верстку

HTML, CSS, JavaScript, Bootstrap

4

Разрабатываем ER-диаграмму базы данных

MySQL Workbench

5

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

Flask

6

Разбиваем HTML версию веб-приложения на шаблоны

Jinja2

7

Создаем модели базы данных на основе ER-диаграммы

SQLAlchemy

8

Создаем view-функции для генерации шаблонов, а также формы для диалога с пользователем

Flask

9

Реализуем регистрацию через OAuth 2.0 и классическую регистрацию через сайт

Flask, requests

10

Реализуем WYSIWYG-редактор для создания контента

CKEditor

11

Реализуем полнотекстовый поиск по контенту сайта

Elasticsearch

12

Реализуем обход блокирующих операций (отправка писем по электронной почте)

Redis, Celery

13

Протестируем веб-приложение

pytest

14

Реализуем кэширование страниц

Redis

15

Реализуем логирование

logging

16

Подключим синхронный серверный WSGI-шлюз на нескольких логических ядрах центрального процессора

Gunicorn

17

Подключим HTTP-сервер и обратный прокси-сервер

nginx

18

Создадим docker compose для объединения всех компонентов в единое целое

Docker

19

Развернём веб-приложение на удаленном сервере

VPS

20

Сделаем взаимодействие между репозиторием нашего проект и удаленным сервером посредством CI/CD

GitHub Actions

Самое главное преимущество всех этих технологий в том, что они бесплатные (кроме VPS, за который платит заказчик). Поэтому от нас потребуется только время — именно это самый главный ресурс в процессе разработки.

Здесь я описал свой персональный алгоритм работы, который для меня является наиболее оптимальным. Ваш алгоритм может отличаться.

ШАГ 1. Изучаем техническое задание

Любая разработка начинается с получения технического задания от заказчика. При изучении ТЗ необходимо максимально вчитываться в каждую строку и если что-то звучит двусмысленно или непонятно, нужно как можно раньше провести бриф с заказчиком для уточнения.

В данном случае заказчиком выступил мой друг с высший медицинским образованием, закончивший ординатуру по специальности «Психиатрия». Сайт будет посвящён оказанию психотерапевтической помощи.

На сайте должны быть новости, статьи, авторский учебник психотерапии, сервис «Вопрос-Ответ», сервис «Записаться на консультацию», а также возможность регистрации в один клики через популярные сервисы Goggle и Яндекс, социальные сети VK (Вконтакте) и Одноклассники, а также через мессенджер Telegram. Регистрация дает пациенту вести дневник и отслеживать прогресс по изучению статей и учебника. Также должен быть реализован полнотекстовый поиск по учебнику. Сайт должен красиво выглядеть и быстро работать.

ШАГ 2. Создаём дизайн веб-приложения UX/UI

UX (User Experience) - это опыт пользователя при взаимодействии с приложением. Это именно те эмоции, которые испытывал пользователь, когда им пользовался. Наша задача в заключается в том, чтобы пользователь имел положительный опыт от использования приложения.

Совет 1: Копируйте расположение элементов на странице с популярных сайтов (VK, YouTube). Например, расположение кнопки регистрации в верхней правой части экрана.

Совет 2: Используйте теорию цвета. Цвета сайта должны сочетаться друг с другом. Лично я для определения сочетаемости использую данные палитры

Мы разобрались с UX, теперь перейдём к UI

UI (User Interface) — это интерфейс пользователя, то есть визуальные элементы и дизайн, через которые пользователь взаимодействует с приложением.

Для создания интерфейса на данный момент самым удобным инструментом является онлайн-приложение Figma. Если вы никогда не работали с Figma, то вам потребуется где-то 3-4 часа, чтобы во всем разобраться.

Я приведу скриншот работы с Figma на раннем этапе разработки дизайна

Проектирование дизайна в Figma на раннем этапе разработки
Проектирование дизайна в Figma на раннем этапе разработки

ШАГ 3. Пишем HTML версию веб-приложения, подключаем CSS и JavaScript, делаем адаптивную верстку

Дальше от нас требуется создать HTML версию сайта. То есть надо создать набор связанных HTML-страниц. Тем самым мы получим статическую версию приложения. Потом мы эти страницы «нарежем» на шаблоны.

Прелесть HTML и CSS заключается в том, что это декларативные языки. То есть мы пишем то, что хотим увидеть. Но главное даже не это. Научиться верстать можно буквально за месяц, благо в интернете огромное количество бесплатных ресурсов.

Современный CSS дает огромные возможности и в частных случаях может полностью заменить JavaScript. Но не всегда…

JavaScript – это императивный язык с Си-подобным синтаксисом (Си — язык программирования). Мы его будем использовать потому, что в нашем приложении будет интерактивность и асинхронные запросы, без перезагрузки страницы. А только с помощью CSS это невозможно реализовать.

JavaScript можно изучать очень долго. Для нашего случая достаточно освоить базовый синтаксис и принципы работы с DOM (Document Object Model — это программный интерфейс для работы с документами, представленными в виде HTML).

И еще по поводу JavaScript: как-то мой друг-фронтендер сказал фразу, которая у меня отпечатались на всю жизнь в голове: «Все JS-скрипты уже давно написаны». И это действительно так. Есть наборы уже готовых JS-скриптов. Для их получения мы воспользуемся фреймворком Bootstrap от Twitter.

Bootstrap хорош тем, что позволяет создавать верстку, которая уже является адаптивной — то есть автоматически подстраивается под разрешение экрана пользователя.

Теперь я дам несколько советов:

Совет 3: Никогда не реализуйте загрузку из интернета CSS и JS-файлов при открытии страницы (CDN и т. п.). Скачайте эти файлы один раз, сохраните на SSD и именно оттуда подключайте, то есть из локального хранилища. Мы живем в мире блокировок ресурсов для «дорогих россиян» и если ваша страница не сможет загрузить эти файлы из интернета, то приложение развалится.

Совет 4: Используйте автоопределение светлой и темной темы приложения, а также дайте пользователю возможность спокойно их переключать. Это напрямую связано с UX. Вечером и ночью пользователю комфортнее пользоваться темной темой, а утром и днем — светлой.

Совет 5: Грамотно реализуйте адаптивную верстку. У браузера Google Chrome сочетание клавиш Ctrl + Shift + I вызывает «Инструменты разработчика». Здесь мы можем посмотреть как будет выглядеть наше приложение на разных устройствах: компьютерах, планшетах, смартфонах. Если что-то где-то выглядит плохо — смело идём в CSS и переопределяем @media правила.

Совет 6: Если сами не можете написать JS-скрипт, спрашивайте у нейросетей (например, ChatGPT). С простыми скриптами нейросеть справляется отлично. Но, как говорится, доверяй, но проверяй.

ШАГ 4. Разрабатываем ER-диаграмму базы данных

ER-диаграмма (Entity-Relationship Diagram) — это графическое представление структуры базы данных, которое показывает объекты (сущности) и связи между ними. Лично мне выделять сущности помогает визуальное представление веб-приложения (что мы реализовывали в ШАГЕ 2 и ШАГЕ 3).

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

Совет 7: Не переживайте, что в процессе разработки вы можете по 15 раз на дню менять схему базы данных. Это нормально. Миграции данных со старой схемы на новую никто не отменял.

Теперь поговорим про инструменты для создания схемы базы данных. Я пробовал, как онлайн-инструменты, там и desktop приложения. Лично для меня самым лучшим инструментом оказался MySQL Workbench

Работа в MySQL Workbench
Работа в MySQL Workbench

ШАГ 5. Создаем структуру приложения: точку входа и соответствующие пакеты

И вот здесь мы переходим к нашему любимому бекенду. Если я утверждал, что вам не нужны глубокие знания JavaScript для реализации фронтенда данного приложения, то здесь я скажу следующее: вам НУЖНЫ глубокие знания Python для реализации бекенда. Причем я говорю не только о базовом синтаксисе и ООП, я имею ввиду еще навыки решения алгоритмических задач, выбор алгоритма наиболее приближенного к O(1), умение работать с библиотекой re для регулярных выражений, умение выполнять HTTP-запросы с помощью библиотеки requests, знать и понимать, что такое глобальная блокировка интерпретатора GIL, и многое другое.

Если выбирать между Django и Flask, то мне предпочтительнее Flask. Django – это гигантский фреймворк с огромным количеством магии и функций, которые вы никогда не будете использовать. Flask, напротив, очень минималистчен, и вы сами решаете какие расширения подключать: хотите админку — flask-admin, хотите миграции — flask-migrate и так делее. Также мне нравится система маршрутизации во Flask с помощью Blueprints. Поставил декоратор на view-функцию с названием эндпоинта и не надо писать никаких urls.py.

Вначале определим точку входа конструкцией if __name__ == “__main__”, из-под этой конструкции будет запускаться наше приложение. Это определим в файле manage.py (привет всем, кто любит Django). А также файл с конфигурациями config.py.

Далее создаем пакеты — это такие папки, где присутствует файл __init__.py. Без этого файла Python не будет рассматривать папку как пакет, и мы не сможем импортировать модули из этой папки. Как раз таки в главной директории приложения в файле __init__.py мы и реализуем функцию создания приложения.

И здесь надо вспомнить про паттерн проектирования «Фабричный метод» (Factory Method). Такой подход к организации создания экземпляра приложения помогает управлять конфигурацией, расширяемостью и тестируемостью. Данный паттерн особенно полезен при работе над большими проектами.

Например, один экземпляр приложения со своей конфигурацией будет относится к стадии разработки, другой — к стадии тестирования, а третий — к стадии Production.

Ссылка на организацию файлов внутри проекта

ШАГ 6. Разбиваем HTML версию веб-приложения на шаблоны

Когда вы устанавливаете Flask в своё виртуальное окружение, то можете заменить, что вместе с ним была установлена библиотека Jinja2.

Jinja2 — это мощный и гибкий шаблонизатор Python для создания динамических веб-приложений с чистым разделением логики и представления. Он используется во многих веб-фреймворках, включая Flask. У Jinja2 есть встроенный механизм защиты от XSS-атак.

Теперь мы должны превратить наш статический HTML-сайт (например, http://localhost/news.html) в динамически генерируемый (http://localhost/news).

Здесь мы делаем разбиение элементов статического сайта на отдельные узлы и помещаем их в шаблоны.

{% if g.search_form %}
  <div class="search-form">
      <form class="d-flex justify-content-around" role="search" method="get" action="{{ url_for('site.search') }}" onsubmit="return submitForm()" novalidate>
        {{ g.search_form.q(size=20, class='form-control me-2', placeholder=g.search_form.q.label.text) }}
        <button class="btn btn-warning">Найти</button>
      </form>
  </div>
{% endif %}

Совет 8: Не прописывайте в шаблоне относительные пути к локальным файлам. Пользуйтесь функцией url_for с указанием этим локальных файлов.

ШАГ 7. Создаем модели базы данных на основе ER-диаграммы

Теперь необходимо создать нашу базу данных. Ранее мы создали ER-диаграмму. И здесь самым лучшим вариантом является выбор ORM SQLAlchemy.

Есть 2 способа реализации алхимии во Flask: 1) Использование дополнения Flask-SQLAlchemy 2) Использование чистой SQLAlchemy.

В чем разница? У Flask-SQLAlchemy есть автоматическое управление жизненным циклом сессий, избавляя нас от необходимости явно открывать и закрывать сессии, а также упрощенная конфигурация. Хорошо ли это? Не всегда. Если мы хотите иметь полный контроль над использованием SQLAlchemy, тогда стандартная версия SQLAlchemy будет лучшим выбором. Конкретно для нашего приложения я принял решение, что полный контроль будет избыточным, а поэтому использовал Flask-SQLAlchemy.

Совет 9: Если вам нужен полный контроль над сессиями баз данных, в случае работы с большими данными, в микросервисной архитектуре, в проектах с высокой производительностью и оптимизацией, то используйте чистую SQLAlchemy. Во всех остальных случаях используйте Flask-SQLAlchemy.

Принцип создания моделей такой же как в Django ORM. Помните в Django атрибут модели ForeignKey для указания внешнего ключа и параметр related_name. В SQLAlchemy все реализуется с помощью функции relationship, в параметры которой прописываются все необходимые данные. Одним из таких параметров является lazy – тип загрузки. Различают 3 типа загрузки: ленивую (lazy loading), жадную (eager loading) и явную загрузку (explicit). Что и когда применять?

Тип загрузки

Значение параметра lazy в relationship(lazy=” значение”)

Ленивая загрузка (lazy)

- select (используется по умолчанию)
- dynamic (устаревшее)

Жадная загрузка (eager)

- joined
- selectin
- subquery
- immediate

Явная загрузка (explicit)

- write_only
- noload
- raise
- raise_on_sql

Совет 10. Установите Flask Debug Toolbar (аналог Django Debug Toolbar) и посмотрите на количество запросов и время отклика для «ленивой», «жадной» и «явной» загрузок. Выберите тип загрузки с наименьшим временем и количеством запросов в базу данных.

ШАГ 8. Создаем view-функции для генерации шаблонов, а также формы для диалога с пользователем

Здесь все предельно просто. Мы создаем функции представлений (view-функции), которые будут отрисовывать шаблон и передавать в него необходимую информацию из базы данных, если требуется. Также мы создаем формы и прописываем валидацию форм. И здесь надо помнить о следующем: информация записывается в базу данных только в самую последнюю очередь — когда данные прошли валидацию формы. Но если даже данные успешно прошли валидацию формы, то могут сработать какие-нибудь ограничения в базе данных. Мы получим ошибку на уровне базы данных.

Совет 11. Помимо валидаторов форм из «коробки» прописывайте свои кастомные валидаторы, чтобы полностью покрыть проверку данных. Не надо допускать возбуждения исключения на уровне базы данных.

Ссылка на views.py проекта

ШАГ 9. Реализуем регистрацию через OAuth 2.0 и классическую регистрацию через сайт

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

Что касается авторизации через социальные сети по протоколу OAuth 2.0, то это обширная и огромная тема.

Чтобы длина статьи не стремилась в бесконечность, я создал подробнейший PDF-гайд о реализации авторизации через Goggle, VK, Одноклассники, Яндекс, Telegram. Там я шаг за шагом в мельчайших подробностях объясняю куда переходить, что вводить, как регистрировать приложение, как получать токены, куда их оправлять и т. д. Все шаги проиллюстрированы скриншотами.

Также для лучшей наглядности я также записал видео с подробным объяснением теории и практики на примере Google по этой теме:

ШАГ 10. Реализуем WYSIWYG-редактор для создания контента

Вы читаете эту статью на Хабре, где авторы выкладивают свои статьи и сообщество их оценивает. Ядром бизнес-логики Хабра является инструмент для создания статей, или WYSIWYG-редактор.

WYSIWYG-редактор (What You See Is What You Get) — это тип редактора, который позволяет пользователям создавать и редактировать контент (например, текстовые документы, веб-страницы или электронные письма) в визуальном формате, где-то, что они видят на экране, соответствует тому, как будет выглядеть конечный продукт. Это делает процесс редактирования более интуитивным и доступным, особенно для людей, не обладающих знаниями в области программирования или веб-разработки.

Хорошая новость в том, что нам ничего самим создавать не придется. Существует встроенные WYSIWYG-редакторы. И самым популярным является CKEditor, который предоставляет все необходимые функции.

Наша задача только интегрировать CKEditor во Flask. И тут нам на помощь приходит расширение Flask-CKEditor.

Когда мы установим это расширение нам необходимо его правильно настроить и у нас на выбор есть 2 способа подключения: онлайн-подключение CKEditor и подключение CKEditor с нашего SSD (локальной версии).

Совет 11: Всегда используйте локальную версию CKEditor с вашего SSD и никогда не загружайте онлайн-версию CKEditor.

Здесь принцип такой же как в совете 3. Удаленный ресурс могут заблокировать. Даже если его никогда не заблокируют, я читал форум, когда у разработчика загружалась онлайн-версия CKEditor 4. А потом эту онлайн-версию обновили до 5 и начала загружаться CKEditor 5. И у него поломалась вся логика проекта. Напоминает обновления Windows.

Использование WYSIWYG-редактора CKEditor
Использование WYSIWYG-редактора CKEditor

Здесь есть один очень интересный момент: это организация файлов картинок. Я создал алгоритм для автоматической конвертации картинки из любого формата изображения в формат WEBP. Это современный формат растровой графики, который пришел на смену PNG.

Также у нас в ER-диаграмме есть отдельная сущность Image. Для чего она создана? Когда мы добавляем какие-нибудь картинки в статью, они сохраняются на SSD, а также в базе данных появляется соответствующая запись. В случае, если мы редактируем статью и у нас уже нет этого изображения, база данных регистрирует, что изображение удалено из неё и у нас срабатывает регистрация событий SQLAlchemy, которая берет абсолютный адрес удаленной картинки и удаляет картинку с SSD. Таким образом, у нас нет лишнего «мусора» на SSD – локально хранятся только те картинки, которые используются. Логика удаления довольно сложная.

Ссылка на файл по реализации логики WYSIWYG-редактора

ШАГ 11. Реализуем полнотекстовый поиск по контенту сайта

Полнотекстовый поиск — это важная задача в разработке веб-приложений, и реализация его во Flask может быть выполнена различными способами. Два распространенных подхода — это использование полного сканирования таблицы базы данных и применение специализированного инструмента для поиска Elasticsearch.

Выбор инструмента зависит от нашего проекта и того, что мы ищем. Самое главное помнить, что при полном сканировании таблиц мы нагружаем нашу базу данных.

Совет 12: Если объем данных небольшой, а требования к поиску просты, выбираем полное сканирование таблицы. Если приложение требует высокой производительности, масштабируемости и гибкости в поисковых запросах, выбираем Elasticsearch.

Elasticsearch — это распределенная поисковая система на базе Apache Lucene, которая предоставляет мощные возможности для полнотекстового поиска. Существует специальная библиотека для Python.

Настройка довольно сложна, и ей должна быть посвящена отдельная статья. На Хабре такая статья существует.

Пример вывода результатов поиска на сайте с помощью Elasticsearch
Пример вывода результатов поиска на сайте с помощью Elasticsearch

ШАГ 12. Реализуем обход блокирующих операций (отправка писем по электронной почте)

Что является одним из самых плохих явлений с точки зрения UX? Я ответил бы так: когда сайт долго обрабатывает запросы. Пользователь ждет, а потом закрывает вкладку и никогда больше на ваш сайт заходит, зная что сайт «глючный».

У нас в коде могут встречаться длительные операции, которые требуют ожидания. Одной из таких операций является отправка письма на электронную почту. Мы должны обойти эту блокирующую операцию. Каким образом? Здесь стоит обратиться к официальной документации Flask, которая нам в таких случаях рекомендует использовать асинхронную очередь Celery и NoSQL СУБД Redis. Именно их мы применим в нашем проекте.

ШАГ 13. Протестируем веб-приложение

Точно не помню в какой книге я это встречал, но мне запомнилась одна фраза: «Если код не протестирован, значит он нерабочий». В Python для тестирования существует стандартная библиотека unittest. Также на практике часто можно встретить использование сторонней библиотеки pytest

Совет 13: Unittest может быть полезна для простых случаев и тех, кто предпочитает более традиционный подход к тестированию. Для более сложных проектов благодаря своей гибкости и мощным возможностям используем pytest.

Я использовал библиотеку pytest из-за возможностей фикстур.

Вначале нам надо в корне проекта создать папку «tests» и сделать её пакетом, то есть поместить туда файл __init__.py. Далее необходимо создать файл conftest.py. Это специальный файл в библиотеке pytest, который используется для настройки тестов и их окружения. Он позволяет определять фикстуры и конфигурации, которые могут быть доступны во всех тестах в директории и ее поддиректориях.

Ну а дальше создаем сами файлы тестов, начинающиеся с «test_что_тестируем»

Автотесты проекта лежат здесь

ШАГ 14. Реализуем кэширование страниц

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

У Flask есть расширения для все случаи жизни. Поэтому мы будем использовать библиотеку Flask-Caching.

Есть несколько инструментов для кеширования. Например, кэширование в памяти — это стратегия, при которой данные временно хранятся в оперативной памяти для обеспечения быстрого доступа к ним. Проблема в том, что объем оперативной памяти ограничен, и при увеличении объема данных кэширование в памяти может стать неэффективным. Эту проблему решает использование Redis в качестве кэш-сервера.

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

В нашем проекте будет использоваться Redis для кэширования. Расширение Flask-Caching предоставляет очень удобный интерфейс для управления Redis.

ШАГ 15. Реализуем логирование

В Production никакой отладки нет, она отключена. Но приложения тем не менее иногда падают. И нам надо бы знать причины — почему? Поэтому мы создаем специальный файл-журнал, куда будут прописываться все события, которые происходят в приложении. Процесс записи событий в этот файл называется логированием. Кстати, логирование можно включать и в процессе разработки, выводя логи в консоль.

Совет 14: При реализации логирования всегда ограничивайте размер файла логов (например, 100 МБ).

Если не установить ограничения файла логирования, то он может разрастись до неописуемых масштабов. Также необходимо определить уровень логирования. Логи какого именно приоритета будут записываться в файл

Организацию логирования можно посмотреть здесь

ШАГ 16. Подключим синхронный серверный WSGI-шлюз на нескольких логических ядрах центрального процессора

WSGI (Web Server Gateway Interface) — это стандартный интерфейс между веб-серверами и Python-приложениями или фреймворками. Он позволяет разработчикам создавать веб-приложения, которые могут работать с любым WSGI-совместимым сервером, обеспечивая гибкость и совместимость.

Самым популярным WSGI-шлюзом является Gunicorn. В соврменном мире почти все процессоры имеют несколько физических ядер. Физическим ядрам соответсвует удвоенное количество логических ядер. Мы можем использовать эти ядра для параллельной обработки запросов. Причем речь идет об истинном параллелизме. Каждый рабочий процесс может обрабатывать свои собственные запросы независимо от других. Это позволяет значительно увеличить пропускную способность приложения, особенно при обработке большого количества одновременных запросов.

Мы можем посмотреть на сайте-хостинге, сколько ядер у нашей виртуальной машины. Но лучше автоматизировать этот процесс, чтобы автоматически определялось количество ядер.

Для этого необходимо реализовать следующий скрипт gunicorn.conf.py:

import multiprocessing
import socket
import fcntl
import struct


def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,
        struct.pack('256s', bytes(ifname[:15], 'utf-8'))
    )[20:24])


ip_address = get_ip_address('eth0')

bind = f'{ip_address}:5000'
workers = multiprocessing.cpu_count()*2 + 1

timeout = 2
preload = True
loglevel = 'info'

И подключить его:

gunicorn -c ./gunicorn.conf.py manage:app

ШАГ 17. Подключим HTTP-сервер и обратный прокси-сервер

Мы на финишной прямой. Теперь надо сделать так, чтобы наше Python-приложение было доступно по определенному URL по протоклу HTTPS. Для этого мы должны создать наш собственный сервер nginx с нашей конфигурацией.

Совет 15: Не пользуйтесь стандартной конфигурацией nginx. В интернете полно гайдов как улучшить конфигурацию, чтобы nginx работал намного быстрее и безопаснее.

ШАГ 18. Создадим docker compose для объединения всех компонентов в единое целое

Теперь мы должны объединить все компоненты нашего приложения в единое целое, связанное одной сетью. Docker – это система для запуска приложения в контейнерах. Самым главным преимущество такого подхода является то, что будь то вы работает на Windows, будь то на MacOS, будь то на Ubuntu – везде результат запуска Docker контейнеров будет одинаковым.

Особое внимание здесь следует уделить подключаемым томам. Это так называемые SSD внутри вашего приложения. Вы можете менять СУБД, пересоздавать образы и контейнеры. Но информация в томах останется не тронутой. Поэтому мы создаем подключаемые тома для хранения картинок и базы данных. И еще дополнительно для Elasticsearch и Redis.

Совет 16: Следите за сохранностью данных, используя подключаемые тома к контейнерам, делайте бекапы.

ШАГ 19. Развернём веб-приложение на удаленном сервере

Пришло время аренды виртуальной машины. Когда вы регистрируетесь на каком-нибудь хостинге, помимо виртуальной машины вам предлагают получить SSL-сертификат и доменное имя. И, как правило, вам дают бонус — бесплатное получение сертификата и имени на полгода.

SSL-сертификат (Secure Sockets Layer) — это цифровой сертификат, который используется для обеспечения безопасного соединения между веб-сервером и браузером. Он шифрует данные, передаваемые между клиентом и сервером, защищая их от перехвата и несанкционированного доступа.

Совет 17: Если на вашем сайте есть аутентификация всегда получайте и подключайте SSL-сертификат

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

Далее на сервере необходимо установить Git или Docker.

Доменное имя, указанное в сертификате, необходимо привязать к IP-адресу виртуальной машины. Это можно также сделать на хостинге.

Совет 18: Если хостинг предлагает виртуальную машину с уже установленным Git и Docker – соглашайтесь. Там уже будет все настроено и вам ничего не придется дополнительно настраивать.

Далее необходимо настроить вход на удаленный сервер по протоколу SSH. Как это делается должно подробно быть написано в документации хостинга. После этого мы создаем на удаленном сервере SSH ключи — публичный и приватный. И эти ключи указываем на GitHub, чтобы можно было скачивать репозитории с GitHub.

Далее необходимо клонировать наш репозиторий с GitHub на удаленный сервер, и выполнить docker compose. С этого момента мы можем при вводе нашего доменного имени переходить на сайт из любого браузера.

ШАГ 20. Сделаем взаимодействие между репозиторием нашего проект и удаленным сервером посредством CI/CD

Все работает, что нам еще надо? А что если мы хотим добавлять новые фичи в наш проект и НЕ хотим вручную все перезаливать на удаленный сервер. Эту проблему решает GitHub Actions, на которых реализуется CI/CD.

CI (Continuous Integration) - это практика, при которой разработчики регулярно объединяют свои изменения в центральный репозиторий. Каждый коммит запускает автоматическое тестирование, что позволяет быстро выявлять и исправлять ошибки.

CD (Continuous Deployment / Continuous Delivery) - полная автоматизация, где каждое изменение, прошедшее тесты, автоматически разворачивается в производственной среде без ручного вмешательства.

Совет 19: На поздних этапах разработки можно создать проект-заглушку для реализации nginx, CI/CD и на этом проекте протестировать и отработать все интеграции и деплоймены.

Все, теперь все изменения в нашем проекте при пуше на GitHub будут автоматически отражаться на удаленном сервере.

Заключение

Напоследок я дам вам совет 20: лично по моему мнению, глубокое изучение бэкэнд технологий имеет более высокий приоритет, чем фронтенд. Если вы поймете как работает бэкэнд, глубоко изучите язык программирования бекэнда, то освоить фронтенд будет довольно просто.

Проект имеет открытый исходный код и полностью доступен здесь

Если у вас возникнут какие-нибудь вопросы, буду рад на них ответить. Спасибо за внимание.

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


  1. DrArgentum
    02.08.2024 15:26
    +2

    Как раз мне не хватало что то такого. Давно хотел попробовать изучить некоторые технологии.

    Автору спасибо, сам люблю фласк за то что минималистичный, быстро настраивается


  1. stvoid
    02.08.2024 15:26
    +2

    Аж глаз радуется.
    Бегло пробежал глазами, но это в любом случае идет в закладки, чтобы удобно кидать падаванам как заметку.

    Я правда отказался от gunicorn в пользу uWSGI, но это слишком спцифичный кейс когда тредпулы используются внутри хэндлера фласка :))


  1. omaxx
    02.08.2024 15:26
    +1

    Правильно. Так и надо. Это только лохи мега-учебники по Flask на несколько десятков статей пишут. А тут бац-бац и все в одну статью вместилось.


    1. kompilainenn2
      02.08.2024 15:26

      что, всё так плохо?


      1. janvarev
        02.08.2024 15:26
        +1

        Да как сказать...

        Не в обиду автору, но его статья лучше всего описывается "рисуем овал - рисуем оставшуюся часть совы"

        Он как будто за все хорошее и против всего плохого - со всеми вытекающими.


        1. surly
          02.08.2024 15:26
          +1

          А по мне, так как раз очень хорошо: «Будем использовать такой подход; основы вот; а подробности читать по этой ссылке». Без лишней воды и теоретизирования охватили всю разработку системы общим взглядом.


        1. Andrey_Solomatin
          02.08.2024 15:26
          +1

          Посмотреть на весь процесс с высоты тоже полезно. Чтобы нарисовать сову, неплохо бы на неё сначало посмотреть.

          В Барселоне есть собор со статуями слонов, которые делались людьми которым рассказывали как выглядит слон. Вышло так-себе. Это собор святого креста и святой Евлалии. Доберусь до компа найду фоточки.


  1. NekkittAY
    02.08.2024 15:26
    +1

    Хорошая статья, кратко охватили основы Backend и Frontend разработки, но почему не использовать FastAPI для разработки и объяснения backend части? FastAPI - довольно легкий и удобный фреймворк с большим функционалом, который, как по мне, понятнее, чем Flask для понимания REST и для реализации микросервисной архитектуры с основ, что пригодится для бедующей разработки веб-приложений.


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

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

      Вы абсолютно правильно заметили, что FastAPI хорош для микросервисной архитектуры с реализацией приложения в виде SPA. У меня сейчас в работе проект, где именно микросервисная архитектура и там я использую FastAPI


    1. Andrey_Solomatin
      02.08.2024 15:26

      Без разницы какой фреймворк, главное есть ли нужные библиотеки под него.


  1. KonstantinTokar
    02.08.2024 15:26

    После этого мы создаем на удаленном сервере SSH ключи — публичный и приватный. И эти ключи указываем на GitHub, чтобы можно было скачивать репозитории с GitHub.

    Так ли это? Приватный?


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      Публичный ключ указываем в глобальных настройках GitHub, чтобы можно было клонировать репозиторий. А приватный ключ указываем в Secrets репозитория нашего проекта, чтобы можно было реализовать CI/CD через GitHub Actions.

      Есть супер объяснение как эти ключи получать и что куда класть - https://www.youtube.com/watch?v=V2YYhGn3MGo&ab_channel=Chaby%27sTech

      У нас приватный ключ будет использован в CI/CD

      cd:
          needs: ci
          name: Update server buy ssh
          runs-on: ubuntu-latest
          steps:
            - name: Connect and run scripts
              uses: appleboy/ssh-action@v1.0.3
              with:
                host: ${{ secrets.HOST }}
                username: ${{ secrets.USERNAME }}
                key: ${{ secrets.SSH_PRIVATE_KEY }}
                port: ${{ secrets.PORT }}
                script: |
                  whoami
                  cd ${{ secrets.PROJECT_FOLDER }}
                  git checkout main
                  git pull
                  docker compose down
                  docker builder prune --force
                  docker image prune --force
                  docker compose up -d --build


  1. Geminix
    02.08.2024 15:26

    Сайт работает? Отображается только лого, месяц и две ссылки на гитхаб и телегу.


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      Открытие сайта в сентябре, сейчас идет наполнение контентом. Пока заглушка на сервере стоит. Когда контентом будет заполнен, сайт откроется.

      Кстати хорошо, что сказали. Я уже уточнил на заглушке, что происходит и когда открытие сайта.

      В публичном репозитории все работает для запуска с локальной машине, но у сайта на сервере будет больше фич


  1. n0ne
    02.08.2024 15:26

    Такое впечатление, что статья написана джуном+, который открыл некоторые фишки и безапелляционно вещает об этом миру. Некоторые советы вообще из разряда советов Остера. Ну и вишенкой на торт: долгие рассказы, а сайт не работает (-:


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      Сайт работает на сервере

      Кстати когда будет открытие - приглашаю вас. Я почитал ваши комментарии, посмотрел ваш рейтинг - я думаю материалы сайта вам сильно пригодятся, у вас прослеживается определенная патология


      1. n0ne
        02.08.2024 15:26

        Т.е. Вы еще не только джун, еще и диагнозы ставите, молодой человек? ((((-:
        Сайт работает на сервере (((-: Иногда лучше молчать (-:
        Составлять мнение по рейтингу и комментариям - это вершина (-:
        Ну а о том, что Вы можете чему-то меня научить... молодой человек, Вы хотя бы лет 10-15 сначала попрограммируйте, а потом этот юношеский максимализм сойдет на нет, вот тогда можно будет о чем-то общаться. На данный момент Вы меня не можете научить ничему от слова совсем (((-:
        Ну и купите себе уже учебник по русскому языку, в конце-то концов


        1. IvanZaycev0717 Автор
          02.08.2024 15:26

          Я встречал в своей жизни много старых токсичных дедов, которые говорили, что "молодежь пошла не та". Только когда дело касается реальных результатов, эти деды ничего, кроме слов показать не могут. Пока молодежь решает задачи бизнеса, деды сидят и ворчат себе под нос. Поэтому современный работадатель относится скептически к найму людей в возрасте.

          По поводу владния русским языком - спасибо за обратную связь, эта проблема у меня ещё со школы - в аттестате только единственная четверка по русскому языку была


          1. n0ne
            02.08.2024 15:26

            А у меня и этой "4" даже не было (((-:
            Да, мы все деды безработные (((-: нас увольняют, никто не берет на работу, ведь кому нужны опыт и знания, когда можно набрать неграмотных выскочек, у которых нет ни опыта, ни знаний и к тому же "проблемы в ясном изложении своих мыслей"?! Выбор очевиден.
            И да: у меня в команде все младше меня и никаких проблем, когда у людей нет нарциссическое расстройства (-:


            1. IvanZaycev0717 Автор
              02.08.2024 15:26

              Вот и идите учите свою команду. Что вы сделали для сообщества Хабра? Ни одной публикации, ни одной ссылки на репозитории, карма минус 10. Вы пожоже на глиста, ленточного червя, паразит одним словом, который перепупал комментарии на Хабре с туалетом. Жалко как на YouTube вас нельзя забанить и удалить ту чушь, которую вы пишите. Пошёл вон отсюда


              1. n0ne
                02.08.2024 15:26

                Ну наконец-то вылезло настоящее нутро инфоцыгана и реальные интеллектуальные спасобности ((-:


                1. IvanZaycev0717 Автор
                  02.08.2024 15:26

                  Какое инфоцыганство? Я ни с кого ни одной копейки не взял и не собираюсь. Я, наоборот, все абсолютно бесплатно для сообщества выложил и для молодежи, пусть посмотрят реальный проект. Там чистый код, соразмерные функции - может ребята что-то для себя почерпут.

                  Проект будет вести мой друг с высшим медицинским образованием. Это дипломированный специалист.

                  И причем тут мои интеллектуальные "спАсобности"? Я никогда себя не позиционировал как умного человека, никаких олимпиад не выигрывал. Да, что-то знаю, но не более того.

                  По поводу грубости с моей стороны, так вы первые начали. Когда человек со мной общается уважительно, то я с ним тоже уважительно. Я ко всем уважительно отношусь: будь-то к гендиру или к уборщице. Но когда человек изнчально ко мне относится как к скоту - я в ответ к ниму буду также относиться.

                  Ну и напоследок... А что? Вас забанили? Боже, какая неожиданность!


                  1. Andrey_Solomatin
                    02.08.2024 15:26

                    Когда человек со мной общается уважительно, то я с ним тоже уважительно.

                    А про остальных людей кому приходят уведомление о новых комментариях вы подумали?


  1. redfox0
    02.08.2024 15:26

    Только у вас не ER-диаграмма, а сразу физическая модель базы данных минуя логическую.


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      спасибо, что уточнили. Я посмотрю эти темы


  1. Andrey_Solomatin
    02.08.2024 15:26

    Посмотрите на лицензию ElasticSearch. В последних версиях она не бесплатна. Все переходят на его форк OpenSearch.


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      Если верить этой статье https://habr.com/ru/news/537610/ то там есть такая фраза, что "На обычных пользователей Elasticsearch, которые используют эту платформу в качестве бэкенда, изменение типа лицензии никак не повлияет."


      1. Andrey_Solomatin
        02.08.2024 15:26

        Надо будет глянуть. Мы там логи храним.


  1. Andrey_Solomatin
    02.08.2024 15:26

    вам НУЖНЫ глубокие знания Python для реализации бекенда.
    Причем я говорю не только о базовом синтаксисе и ООП, я имею ввиду еще
    навыки решения алгоритмических задач, выбор алгоритма наиболее
    приближенного к O(1), умение работать с библиотекой re для
    регулярных выражений, умение выполнять HTTP-запросы с помощью библиотеки
    requests, знать и понимать, что такое глобальная блокировка
    интерпретатора GIL, и многое другое.

    Да не особо. Алгоритмические задачи пусть база данных решает сама.

    Регулярки для путей тривиальные.

    Requests можно за час выучить. Как и прочие библиотеки для запросов.

    Про GIL не надо знать, у вас есть Celery, она сама за вас многопоточность разрешит. Ну и сам фласк в один поток работает.

    Вы используете много инструментов, которые прячут от разработчика сложность. Можно при средних знаниях языка писать нормальные приложения.


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      Про GIL не надо знать

      Про GIL надо знать, чтобы понимать что происходит, когда мы отправляем письмо через flask-mail. Почему с такой задержкой выполняется!? Именно из этого и возникает потребность в асинхронных очередях, ну или можно реализовать вытесняющую многозадачность с помощью многопоточности, как вариант. Поэтому про GIL надо знать


      1. Andrey_Solomatin
        02.08.2024 15:26

        Вы хотите сказать, что отправка письма из-за GIL тормозит? Если взять версию Питона без него, то письмо уйдёт мгновенно? Асинхронные очереди нужны, чтобы разгрузить работников которые обслуживают пользователей от лишней работы. Это стандартный прием для веб сервисов на разных языках и с разными парадигмами.


        1. IvanZaycev0717 Автор
          02.08.2024 15:26

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

          Если взять версию Питона без него, то письмо уйдёт мгновенно?

          Можно взять версию Питона с ним и переложить на другой поток, тогда оно уйдет мгновенно.


          1. Andrey_Solomatin
            02.08.2024 15:26

            Можно взять версию Питона с ним и переложить на другой поток, тогда оно уйдет мгновенно.

            Чудеса. В главном потоке долго а в другом быстро. Мгновенно будет, если не ждать завершения второго потока и отвечать пользователю сразу.


            1. IvanZaycev0717 Автор
              02.08.2024 15:26

              Чудеса

              Я люблю фэнтизи. И не только я один - https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-x-email-support. Цитата из этой статьи:

              Python has support for running asynchronous tasks, actually in more than one way. The threading and multiprocessing modules can both do this. Starting a background thread for email being sent is much less resource intensive than starting a new process, so I'm going to go with that approach:

              from threading import Thread
              # ...
              
              def send_async_email(app, msg):
                  with app.app_context():
                      mail.send(msg)
              
              
              def send_email(subject, sender, recipients, text_body, html_body):
                  msg = Message(subject, sender=sender, recipients=recipients)
                  msg.body = text_body
                  msg.html = html_body
                  Thread(target=send_async_email, args=(app, msg)).start()

              Кстати это пишет один из разрабочичиков нашего любимого Elastic.

              Я давно уже понял, что сколько людей - столько и мнений. Также точно и в медицине: один врач ставит тревогу, другой - депрессию, третий - ОКР. И каждый новый говорит, что предыдущий некомпетентен.


              1. Andrey_Solomatin
                02.08.2024 15:26

                Так это именно то о чем я и говорил. Вы не отправляете письмо когда выходите из send_email. Оно потом когда-нибудь отправится и может потеряться при перезапуске сервера.


                1. IvanZaycev0717 Автор
                  02.08.2024 15:26

                  Видимо мы просто друг друга не поняли. В проекте я использую Celery и не использую этот подход (хотя это вполне возможно сделать).

                  Увы, есть у меня проблема в ясном изложении своих мыслей


                  1. Andrey_Solomatin
                    02.08.2024 15:26

                    хотя это вполне возможно сделать

                    Я бы не рекомендовал использовать многопоточность самостоятельно для веба, там могут быть свои заморочки. И GIL не худшая из них. Это такие вещи, где действительно нужны серьёзные знания. Есть гораздо более надёжные и простые решения. Например:

                    В проекте я использую Celery

                    Это правильный подход. И еще он избавляет вас от необходимости понимания GIL.


  1. Andrey_Solomatin
    02.08.2024 15:26

    Совет 13: Unittest может быть полезна для простых случаев и тех, кто предпочитает более традиционный подход к тестированию

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

    В последних двух компаниях где я работал, тесты пишутся на обоих, но запускаются через пайтест. У него runner лучше.

    Мой совет, используйте только пайтест, никакого юниттест.


    1. IvanZaycev0717 Автор
      02.08.2024 15:26

      соглашусь с вами. Я забыл про это сказать, что unittest можно запустить с помощью pytest.


  1. Andrey_Solomatin
    02.08.2024 15:26

    Также мне нравится система маршрутизации во Flask с помощью Blueprints.
    Поставил декоратор на view-функцию с названием эндпоинта и не надо
    писать никаких urls.py.

    Чуть раньше вы ругали Джанго за магию. Не это ли она самая?

    Концептуально мне подход со всеми путями в одном месте нравится больше. Вы всегда можете легко посмотреть все пути, включая технические, пришедшие из плагинов. Явное лучше не явного. Если у вас большой проект и много разработчиков то так сложнее запутаться. Для простого проекта декоратор конечно удобнее.