Проект “Планировщик задач” #
Многопользовательский планировщик задач. Пользователи могут использовать его в качестве TODO листа. Источником вдохновения для проекта является Trello.
Что нужно знать #
- Python - коллекции, ООП
- pip/Poetry
- Backend
- FastAPI
- Базы данных
- PostreSQL
- SQLAlchemy
- Миграции
- Frontend - HTML/CSS, Bootstrap, Javascript, Ajax
- Docker и микросервисы
- Контейнеры, образы, volumes, написание Dockerfile, Docker Compose
- Брокер сообщений RabbitMQ
- Celery
- DevOps
- Деплой
- CI/CD, GitHub Actions
Мотивация проекта #
- Знакомство с микросервисами на практике - разработка проекта, разделённого на несколько сервисов
- Использование новых библиотек - Celery, smtplib
- Работа с асинхронностью
- Практика с Docker и Docker Compose, докеризация FastAPI приложение (упаковка его в Docker образ)
- Знакомство и практика с RabbitMQ
- Самостоятельная разработка части схемы REST API
Дисклеймер. #
Планировщик задач - довольно простой и легковесный проект для микросервисного подхода. Но проект достаточной сложности вышел бы за рамки учебного по объёму и сложности.
Библиотека Celery работает в синхронном режиме, что имеет ряд недостатков при использовании с асинхронными фремворками типа FastApi:
- Блокирующая операция запуска задач (создание задачи в очереди)
- Нельзя переиспользовать код используемый в асинхронном фреймворке. Возможно будет две версии для некоторых функций - синхронная для celery и асинхронная для FastAPI
Для решения данных проблем на в реальном проекте могут быть выбраны аналоги поддерживающие асинхронную работу - task-iq, Arq, AioTasks. Однако Celery популярнее и чаще требуется в вакансиях при трудоустройстве, а принцип работы данных инструментов довольно похож.
Функционал приложения #
Работа с пользователями:
- Регистрация
- Авторизация
- Logout
Работа с задачами:
- Создание, редактирование (каждая задача имеет заголовок и описание)
- Пометка задачи как сделанной
- Удаление
Оповещение пользователей по email:
- Приветственное письмо
- Каждый день в полночь оповещение о том, сколько задач было сделано за прошедшие сутки, и какое количество осталось невыполненными
Интерфейс приложения #
Веб-приложение является одностраничным, всё общение с сервером происходит с помощью Javascript/Ajax, интерфейс обновляется через jQuery.
- Заголовок
- Для неавторизованных пользователей - кнопки регистрации и авторизации
- По нажатию на кнопки открывается модальный диалог с формой регистрации или авторизации
- Поля диалога регистрации - email, password, repeat password
- Поля диалога авторизации - email, password
- В случае ошибки регистрации или авторизации, приложение должно отобразить ошибку, подобно Thymeleaf
- Для авторизованных пользователей - логин текущего пользователя и кнопка Logout
- Контент (только для авторизованных пользователей)
- Поле ввода заголовка новой задачи с кнопкой “Добавить”
- Список несделанных задач
- Список сделанных задач
При клике на задачу открывается модальное окно со следующими элементами:
- Поле ввода заголовка задачи
- Многострочное поле ввода описания задачи
- Чекбокс, чтобы пометить задачу сделанной (и обратно - пометить несделанной)
- Кнопка “удалить”
У формы модального окна нет кнопки “сохранить”. Javascript должен сохранять состояние задачи с помощью Ajax запросов, каждый раз когда пользователь нажимает на чекбокс, или редактирует поля ввода.
SQL база данных #
В этом проекте студент самостоятельно разрабатывает структуру базы данных для хранения пользователей и задач. Отношение между пользователем и задачами - один ко многим.
Важно помнить о создании необходимых индексов в таблице Users
. Например, логин пользователя должен быть уникальным.
Использовать поддержку токенов, базовой аутентификации, аутентификации OAuth и других методов аутентификации.
Таблица Tasks
должна содержать поля для описания заметки - заголовок, текст, владелец, статус, и время (timestamp), когда задача была помечена как сделанная.
Миграции #
Рекомендую воспользоваться инструментом миграций для создания таблиц. В контексте FastAPI и SQLAlchemy таким инструментом является Alembic. Необходимо будет принять решение, какой из сервисов является “главным” по работе со схемой БД, и содержит в себе миграции.
JWT #
Вместо сессий воспользуемся JWT. Выдавать access token и refresh token пользователю, для авторизации и для обновления access токенов, refresh токены передавать в http-only куках.
Для хранения refresh токенов можно использовать Redis или SQL базу данных.
Сервисы #
Бэкенд #
FastAPI приложение, реализующее REST API для работы с пользователями, авторизацией и задачами.
POST /user
- регистрация пользователя
#
- Тело запроса - поля формы в формате
application/x-www-form-urlencoded
илиapplication/json
- Зарегистрированный пользователь сразу же автоматически авторизуется, без отдельного заполнения логин формы
- В случае успешной регистрации, код ответа HTTP 200, HTTP заголовок ответа устанавливает куку авторизованного пользователя
- В случае неуспешной регистрации, код и тело ответа описывают ошибку
Пример ответа с ошибкой - “this email is already taken” - HTTP code 409:
{
"message": "This email is already taken"
}
Структура ответа с ошибкой - JSON объект с полем message. Код ответа должен быть наиболее подходящим к ситуации из 4хх 5хх кодов ошибок - https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-p2-semantics-18#section-7.4.
Так же, в случае успешной регистрации, пользователю отправляется приветственное письмо (непосредственная отправка письма происходит в другом сервисе, детали ниже в разделе RabbitMQ).
GET /user
- получение текущего пользователя
#
- Успешный ответ с кодом HTTP 200 содержит модель пользователя в виде JSON объекта
- В случае, если пользователь неавторизован, его сессия истекла или прервана через logout - тело ответа содержит сообщение об ошибке, код ответа - HTTP 401
Пример ответа с кодом HTTP 200:
{
"id": 1,
"email": "my@email.com"
}
POST /session
- авторизация пользователя
#
- Тело запроса - поля формы в формате
application/x-www-form-urlencoded
илиapplication/json
- В случае успешной авторизации, код ответа HTTP 200, HTTP заголовок ответа устанавливает куку авторизованного пользователя
- В случае неуспешной авторизации, код и тело ответа описывают ошибку
DELETE /session
- logout
#
- Прекращение сессии пользователя
GET /tasks
- получение списка заметок текущего пользователя
#
- Успешный ответ с кодом HTTP 200 содержит список моделей задач в виде JSON массива
- В случае, если пользователь неавторизован, его сессия истекла или прервана через logout - тело ответа содержит сообщение об ошибке, код ответа - HTTP 401
Дизайн методов для создания, редактирования, удаления задач студенту предлагается разработать самостоятельно.
Фронтенд #
Набор статических файлов:
- HTML разметка структуры главной страницы (динамические данные вставляются через Javascript/jQuery)
- CSS
- Javascript
При разработке, файлы можно раздавать локальным сервером встроенным в Intellij IDEA. В контексте Docker Compose стека, образ фронтенд сервиса будет содержать веб-сервер Nginx, раздающий статику.
Работа с Javascript/Ajax:
- При загрузке страницы, Javascript делает запрос на получение текущего пользователя. Ответ 401 HTTP означает, что пользователь неавторизован, 200 - авторизован. Для авторизованного пользователя мы можем запросить и отобразить список задач
- Формы авторизации и регистрации должны быть сверстаны с помощью тега
form
, но вместо обработчика по-умолчанию (POST запрос и перезагрузка страницы), jQuery будет переопределять обработчик, исполнять POST запрос через Ajax и реагировать на результат, в соответствии с кодом ошибки - Веб-страница и REST API будут доступны на разных портах - 80 и 8080. Для вызова методов REST API через Ajax может потребоваться выдача разрешения браузеру через CORS - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
Рассыльщик писем #
Celery worker приложение с smtplib.
В Celery создаем задачи для отправки писем на почту. Используя брокера Celery подключаем RebbitMQ очередь. Всю работу с очередями берет на себя Celery. Из бекенд приложения мы можем запускать задачи в celery worker.
Для каждого полученного сообщения, содержимое которого десериализуется в экземпляр модели, пользуемся модулем smptlib, чтобы отправить письмо. Подробнее о том, откуда брать SMTP конфигурацию, написано ниже в разделе Mailjet.
Планировщик #
Celery Beat - планировщик для переодичиской рассылки писем.
Задача сервиса - раз в сутки итерировать всех пользователей, и формировать для них отчёты о задачах и изменениях в них за сутки.
- Пользователям, у которых на конец дня есть невыполненные задача, отправляется емейл вида “У вас осталось N несделанных задач”. Тело письма содержит заголовки этих задач (не более какого-то разумного количества, например 5)
- Пользователям, у которых за последние сутки 1 или более задач были отмечены как сделанные, отправляется емейл вида “За сегодня вы выполнили N задач”. Тело письма содержит заголовки выполненных задач
- Пользователям, для которых верны оба условия выше, отправляется емейл и со списком несделанных, и сделанных за последние сутки задач
Celery Beat Scheduler по Cron выражению каждую полночь вызывает метод, читающий список пользователей, анализирующий их заметки, и формирующий емейлы для отправки.
Работа с RabbitMQ #
RabbitMQ в контексте docker compose стека будет запущен из образа, подобно базе данных или хранилищу сессий. Всю работу с очередью берет на себя Celery. Поэтому в рамках работы с RebbitMQ необходимо поднять базу данных в docker, настроить и настроить брокер в приложении.
Mailjet #
Для отправки SMTP нужны логин, пароль, хост и порт SMTP сервера. У емейл сервисов, например Gmail, mail.ru, есть SMTP сервера, с которыми работают емейл-клиенты. Теоретически, можно воспользоваться их сервером, и отправлять письма, авторизовываясь логином и паролем своего ящика, но в реальности, для писем и рассылок обычно пользуются специальными для этого созданными сервисами, например Mailjet, Mailchimp, Mailgun (и десятками аналогичных).
Для примера рассмотрим Mailjet. Бесплатный лимит сервиса - 200 писем на отправку в сутки. Необходимо зарегистрироваться там, сконфигурировать sender адрес (адрес отправителя писем), получить credentials (логин и пароль) и остальные SMTP настройки.
Логин и пароль - секретные данные, которые не должны попасть в git репозиторий. При запуске приложения из IDE или Docker Compose стека, секреты можно подключить к приложению через переменные окружения.
Docker #
В данном проекте воспользуемся Docker для запуска необходимых приложений, и упаковки наших четырех сервисов в Docker образы.
Необходимо:
- Найти образы для каждого нужного приложения
- Написать Docker Compose файл для запуска стека с приложениями (по сервису для каждого)
- Упаковать сервисы в Docker образы, написав для них Dockerfile, если это необходимо
Сервисы:
- task-tracker-backend (порт 8080)
- task-tracker-frontend (порт 80)
- task-tracker-scheduler (celery beat)
- task-tracker-email-sender (celery worker)
- PostreSQL
- RabbitMQ
Для сервисов backend, scheduler, email-sender используется общая кодовая база и поэтому может быть использован один Dockerfile. Конфигурацию и команду запуска задать в docker compose файле.
Структура проекта #
Для структурирования мульти-сервисного проекта существует 2 альтернативных подхода:
- Отдельный репозиторий для каждого сервиса, плюс отдельный репозиторий для Docker Compose. Главный минус - необходимость создавать и управлять несколькими репозиториями
- Единый репозиторий для всех сервисов и Docker Compose. Главный минус - необходимость дополнительной конфигурации.
Выбор того или иного варианта зависит от ситуации. В рамках данного проекта, вариант #2 кажется более предпочтительным.
Деплой #
Приложение целиком будем деплоить через Docker.
Шаги:
- С помощью GitHub Actions (детали ниже) автоматизировать сборку докер образов для сервисов task-tracker-backend, task-tracker-frontend, task-tracker-scheduler, task-tracker-email-sender
- В хостинг-провайдере по выбору арендовать облачный сервер на Linux
- Установить Docker
- Скопировать на удалённый сервер Docker Compose файл для запуска сервисов приложения, запустить
- Опционально - купить доменное имя и привязать к нему IP сервера
Ожидаемый результат - приложение доступно по адресу http://$server_ip
, или http://$domain_name
(если куплено доменное имя).
CI/CD #
С помощью GitHub Actions автоматизируем сборку докер образов для четырёх сервисов приложения (task-tracker-*):
- При push в основную ветку запускается GitHub Actions пайплайн:
- Шаг 1 - сборка образов сервисов. Для информации по выбору базового образа, установки зависимостей в докрере, кешировании и multi stage build рекомендую обратиться в на данный сайт
- Шаг 2 - push образов в публичный репозиторий, откуда их можно будет скачать для деплоя. В качестве публичного репозитория проще всего воспользоваться https://hub.docker.com/. Необходимо будет разобраться с безопасным хранением логина/пароля от Docker Hub на GitHub для push’а образов
План работы над приложением #
- Docker Compose - добавить Postgres, RabbitMQ
- Backend - работа с пользователями
- Backend - работа с jwt (авторизация, логаут)
- Frontend - структура страницы, интерфейс и Javascript для регистрации и авторизации
- Backend - работа с задачами
- Frontend - листинг, добавление, редактирование, удаление задач
- Email sender - реализация задач для отправки емейлов
- Backend - отправка приветственного письма при регистрации
- Scheduler - отправка пользовательских отчетов по сделанным и оставшимся задачам
- CI/CD, деплой
Ресурсы для работы над ошибками #
- Реализации проекта другими студентами и мои ревью этих реализаций
- Готовый проект можете отправить мне на ревью - https://t.me/zhukovsd