Планировщик задач

Проект “Планировщик задач” #

Многопользовательский планировщик задач. Пользователи могут использовать его в качестве TODO листа. Источником вдохновения для проекта является Trello.

Что нужно знать #

  • Go - std lib, http, пакеты, интерфейсы
  • Go modules
  • Backend
    • Echo http router или любой другой роутер
    • HTTP - заголовки, понимание методов
    • Работа с SMTP протоколом
  • Базы данных
    • PostgreSQL
    • Squirrel или обычный драйвер к бд
    • Миграции
  • Frontend - HTML/CSS, Bootstrap, Javascript, Ajax
  • Docker и микросервисы
    • Контейнеры, образы, volumes, написание Dockerfile, Docker Compose
    • Брокер сообщений Kafka
  • Деплой
    • Деплой
    • CI/CD, GitHub Actions

Мотивация проекта #

  • Знакомство с микросервисами на практике - разработка проекта, разделённого на несколько сервисов
  • Использование новых технологий - Kafka, SMTP
  • Знакомство и практика с брокером сообщений Kafka
  • Самостоятельная разработка части схемы REST API

Дисклеймер. Планировщик задач - довольно простой и легковесный проект для микросервисного подхода. Но проект достаточной сложности вышел бы за рамки учебного по объёму и сложности.

Функционал приложения #

Работа с пользователями:

  • Регистрация
  • Авторизация
  • Logout

Работа с задачами:

  • Создание, редактирование (каждая задача имеет заголовок и описание)
  • Пометка задачи как сделанной
  • Удаление

Оповещение пользователей по email:

  • Приветственное письмо
  • Каждый день в полночь оповещение о том, сколько задач было сделано за прошедшие сутки, и какое количество осталось невыполненными

Интерфейс приложения #

Веб-приложение является одностраничным, всё общение с сервером происходит с помощью JavaScript/Ajax, интерфейс обновляется через jQuery.

  • Заголовок
    • Для неавторизованных пользователей - кнопки регистрации и авторизации
    • По нажатию на кнопки открывается модальный диалог с формой регистрации или авторизации
      • Поля диалога регистрации - email, password, repeat password
      • Поля диалога авторизации - email, password
      • В случае ошибки регистрации или авторизации, приложение должно отобразить ошибку, подобно Thymeleaf
    • Для авторизованных пользователей - логин текущего пользователя и кнопка Logout
  • Контент (только для авторизованных пользователей)
    • Поле ввода заголовка новой задачи с кнопкой “Добавить”
    • Список несделанных задач
    • Список сделанных задач

При клике на задачу открывается модальное окно со следующими элементами:

  • Поле ввода заголовка задачи
  • Многострочное поле ввода описания задачи
  • Чекбокс, чтобы пометить задачу сделанной (и обратно - пометить несделанной)
  • Кнопка “удалить”

У формы модального окна нет кнопки “сохранить”. JavaScript должен сохранять состояние задачи с помощью Ajax запросов, каждый раз когда пользователь нажимает на чекбокс, или редактирует поля ввода.

SQL база данных #

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

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

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

Таблица Tasks должна содержать поля для описания заметки - заголовок, текст, владелец, статус, и время (timestamp), когда задача была помечена как сделанная.

Миграции #

Рекомендую воспользоваться инструментом миграций для создания таблиц. Необходимо будет принять решение, какой из сервисов является “главным” по работе со схемой БД, и содержит в себе миграции.

JWT #

Вместо сессий воспользуемся JWT. Можно обойтись без refresh token’ов, выдавать только access token, по которым авторизовывать запросы к REST API.

Стоит реализовать авторизацию JWT на уровне middleware, например, в виде фильтра, который будет доставать access token из запроса, декодировать и валидировать его и добавлять в контекст.

Сервисы #

Бэкенд #

Go приложение, реализующее REST API для работы с пользователями, авторизацией, и задачами.

POST /user - регистрация пользователя #

  • Тело запроса - поля формы в формате application/x-www-form-urlencoded или application/json
  • Зарегистрированный пользователь сразу же автоматически авторизуется, без отдельного заполнения логин формы
  • В случае успешной регистрации, код ответа HTTP 200, HTTP заголовок ответа содержит выданный пользователю JWT access token
  • В случае неуспешной регистрации, код и тело ответа описывают ошибку

Пример ответа с ошибкой - “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.

Так же, в случае успешной регистрации, пользователю отправляется приветственное письмо (непосредственная отправка письма происходит в другом сервисе, детали ниже в разделе Kafka).

GET /user - получение текущего пользователя #

  • Успешный ответ с кодом HTTP 200 содержит модель пользователя в виде JSON объекта
  • В случае, если пользователь неавторизован, его токен истек - тело ответа содержит сообщение об ошибке, код ответа - HTTP 401

Пример ответа с кодом HTTP 200:

{
    "id": 1,
    "email": "my@email.com"
}

POST /auth/login - авторизация пользователя #

  • Тело запроса - поля формы в формате application/x-www-form-urlencoded или application/json
  • В случае успешной авторизации, код ответа HTTP 200, HTTP заголовок ответа содержит выданный пользователю JWT access token
  • В случае неуспешной авторизации, код и тело ответа описывают ошибку

GET /tasks - получение списка заметок текущего пользователя #

  • Успешный ответ с кодом HTTP 200 содержит список моделей задач в виде JSON массива
  • В случае, если пользователь неавторизован, его токен истек - тело ответа содержит сообщение об ошибке, код ответа - HTTP 401

Дизайн методов для создания, редактирования, удаления задач студенту предлагается разработать самостоятельно.

Фронтенд #

Набор статических файлов:

  • HTML разметка структуры главной страницы (динамические данные вставляются через JavaScript/jQuery)
  • CSS
  • JavaScript

При разработке, файлы можно раздавать с помощью Docker Compose стека, образ фронтенд сервиса будет содержать веб-сервер Nginx, раздающий статику.

  • При загрузке страницы, JavaScript делает запрос на получение текущего пользователя. Ответ 401 HTTP означает, что пользователь неавторизован, 200 - авторизован. Для авторизованного пользователя мы можем запросить и отобразить список задач Работа с JavaScript/Ajax:
  • Формы авторизации и регистрации должны быть сверстаны с помощью тега form, но вместо обработчика по-умолчанию (POST запрос и перезагрузка страницы), jQuery будет переопределять обработчик, исполнять POST запрос через Ajax и реагировать на результат, в соответствии с кодом ошибки
  • Веб-страница и REST API будут доступны на разных портах - 80 и 8080. Для вызова методов REST API через Ajax может потребоваться выдача разрешения браузеру через CORS - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Рассыльщик писем #

Go-сервис, отвечающий за асинхронную обработку и отправку email-сообщений.

Обработка сообщений — асинхронная, с использованием паттерна worker pool (фиксированное число горутин-воркеров + буферизованный канал).

Для каждого полученного сообщения, содержимое которого десериализуется в экземпляр модели, используем SMTP протокол, чтобы отправить письмо. Подробнее о том, откуда брать SMTP конфигурацию, написано ниже в разделе Mailjet.

Планировщик #

В проекте сервисы разделены по доменам: домен User принадлежит API-сервису, а cron-сервис отвечает исключительно за планирование и публикацию событий, не управляя доменной логикой.

Go-сервис для управления cron-задачами через gRPC API: создаёт, запускает и отслеживает задачи по расписанию, а при их срабатывании отправляет событие в Kafka, чтобы инициировать рассылку уведомлений пользователям в основном сервисе.

Cron сервис по Cron выражению каждую полночь отправляет ивент на начало нотификации. Основной сервис запускает поиск списка пользователей, анализирующего их заметки, и формирующий емейлы для отправки. Сформированные емейлы упаковываются в структуры и отправляются в Kafka топик.

В основном сервисе нужно реализовать следующую логику:

  • Пользователям, у которых на конец дня есть невыполненные задача, отправляется емейл вида “У вас осталось N несделанных задач”. Тело письма содержит заголовки этих задач (не более какого-то разумного количества, например 5)
  • Пользователям, у которых за последние сутки 1 или более задач были отмечены как сделанные, отправляется емейл вида “За сегодня вы выполнили N задач”. Тело письма содержит заголовки выполненных задач
  • Пользователям, для которых верны оба условия выше, отправляется емейл и со списком несделанных, и сделанных за последние сутки задач

Работа с Kafka #

Kafka в контексте Docker Compose стека запускается образов, подобно базе данных.

  • Задачи для отправки емейлов будут попадать в топик EMAIL_SENDING_TASKS
  • Бэкенд сервис и планировщик записывают сообщения в топик, рассыльщик писем - читает
  • Формат сообщений - JSON, сереализуемый и десериализуемый по модели, описанной с помощью структуры Go с тегами json:"foo", подобно моделям для контроллеров REST API. Сообщения в топике EMAIL_SENDING_TASKS должны содержать следующие поля - адрес получателя, заголовок и текст письма
  • Для интеграции Go c Kafka можно использовать любую библиотеку для Go, например - https://github.com/segmentio/kafka-go

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
  • task-tracker-email-sender
  • Postgres
  • Kafka

Структура проекта #

Для структурирования мульти-сервисного проекта существует 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 Dockerfile.
    • Шаг 2 - push образов в публичный репозиторий, откуда их можно будет скачать для деплоя. В качестве публичного репозитория проще всего воспользоваться https://hub.docker.com/. Необходимо будет разобраться с безопасным хранением логина/пароля от Docker Hub на GitHub для push’а образов

План работы над приложением #

  • Docker Compose - добавить Postgres, Kafka
  • Backend - работа с пользователями
  • Backend - работа с JWT, авторизация
  • Frontend - структура страницы, интерфейс и Javascript для регистрации и авторизации
  • Backend - работа с задачами
  • Frontend - листинг, добавление, редактирование, удаление задач
  • Email sender - реализация получения сообщений от Kafka и отправки емейлов
  • Backend - отправка приветственного письма при регистрации
  • Scheduler - отправка пользовательских отчетов по сделанным и оставшимся задачам
  • CI/CD, деплой

Ресурсы для работы над ошибками #

  • Реализации проекта другими студентами и мои ревью этих реализаций
  • Присылайте законченные проекты в чат, добавляю их в список, сообщество делает ревью проектов