Skip to content

ykkssyaa/Banner_Service

Repository files navigation

Сервис банеров

Описание задачи

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

Общие вводные

Баннер — это документ, описывающий какой-либо элемент пользовательского интерфейса. Технически баннер представляет собой  JSON-документ неопределенной структуры.  Тег — это сущность для обозначения группы пользователей; представляет собой число (ID тега).
Фича — это домен или функциональность; представляет собой число (ID фичи).

  1. Один баннер может быть связан только с одной фичей и несколькими тегами
  2. При этом один тег, как и одна фича, могут принадлежать разным баннерам одновременно
  3. Фича и тег однозначно определяют баннер

Запуск

Все команды описаны в Makefile:

  • make docker.start.components - Запуск всех контейнеров
  • make test.integrations - Запуск интеграционных тестов

Для тестирования API использовался Postman. Все endpoint'ы описаны в данной коллекции.
Переменные описаны в Banner_env

Обновленное API с новыми endpoint'ами можно посмотреть здесь

Важный момент: в репозитории находится .env файл. При обычных условиях я бы его добавил в .gitignore, но для удобства проверки задания я решил его оставить

Ход решения

Здесь я опишу самые важные и интересные вещи, с которыми я столкнулся во время реализации проекта.

Swagger API

Я воспользовался предоставленным API для генерации базового сервера с помощью Swagger Editor. Оттуда я взял реализацию модели баннера и всех роутов с последующими доработками.

К примеру: в качестве JSON контента неопределенной структуры я использовал пользовательский тип ModelMap. Он представляет собой обычную мапу map[string]interface{}. Для этого типа в дальнейшем я переопределил методы маршалинга и анмаршалинга для работы с БД.

Так же Swagger Editor мне помог с генерацией коллекции в Postman

Работа с базой данных

В качестве базы данных я использую PostgreSQL. Схема данных имеет 4 таблицы: Баннеры, фичи, теги и таблица для реализации связи многие-ко-многим у баннеров и тегов. Все таблицы связаны внешними ключами. Из интересного в схеме: баннеры имеют составной первичный ключ из id и version (про версии баннеров чуть позже).

Я долго думал, как бы реализовать момент:

Фича и тег однозначно определяют баннер

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

Миграциями управляет утилита golang-migrate. Ничего устанавливать на систему не нужно, всё запускается с помощью докера Все миграции тоже уже прописаны, среди них есть заполнение таблиц фич и тегов случайными данными(по 1000 значений).

Тестирование

Интеграционные тесты

Я реализовал интеграионные тесты с помощью библиотеки Testify. В частности использовал пакет suite для настройки всех зависимостей и начального состояния перед тестами. Протестировал я только сценарий получения баннеров пользователем/админом. Каждый тест представляет логическое разделение группы тест-кейсов. Перед каждым тестом обновляется кеш и данные в базе данных.

Все тест-кейсы описаны в Notion на этой странице

Кеширование

Для реализации пункта

допускается передача информации, которая была актуальна 5 минут назад

Я решил использовать Redis для кеширования получаемых баннеров. Работает кеширование только на получении баннеров пользователем.

Ключи исчезают через 5 минут, как и требуется. При этом если использовать флаг use_last_revision, сервер сразу обращается к бд без поиска ключей в Redis.

Благодаря кешированию значительно возрастает ответ от сервера, когда баннер запрашивается повторно(5мс вместо +-50мс). Для тестов я сделал возможность отключения кеширования, если в .env файле прописать CACHE_ON=false.

Версии баннеров

Один из интересных пунктов в задании(они все интересные, на самом деле) был

Иногда получается так, что необходимо вернуться к одной из трех предыдущих версий баннера в связи с найденной ошибкой в логике, тексте и т.д. Измените API таким образом, чтобы можно было просмотреть существующие версии баннера и выбрать подходящую версию

Для этого я сделал состаной ключ в базе данных из id и версии баннера. Так же и внешний ключ в many2many таблице имеет два внешних ключа. Триггеры эту логику тоже учитывают и позволяют делать баннеры с одним и тем же тегом и фичей, если они с одним id, но разных версий.

Также для работы с версиями я реализовал 2 новых endpoint'а: Получение всех версий конкретного баннера и выбор активной версии.

Новые версии получаются, когда мы отправляем PATCH запрос на обновление баннера. И вместо обновления создаются новые записи в БД с увеличением version. При баннера выбирается именно активная версия баннера

Остальное

Стек технологий

  1. Golang 1.21
  2. PostgreSQL 16
  3. Redis 7.2
  4. Docker, Docker-compose
  5. Makefile
  6. Viper для считывания конфигурации
  7. Sqlx для работы с БД
  8. Go-redis
  9. Testify
  10. Gorilla/Mux для роутинга

Архитектура

Сервис состоит из 3 слоёв:

  • Gateway (внешние сервисы, БД)
  • Service (валидация, бизнес-логика)
  • Server (Считывание http запросов, валидация аргументов)

Смотря сейчас на сервис я бы разграничил валидацию с бизнес-логикой. Сейчас у меня обработка ошибок находится именно в слое сервиса.

Middleware

В серверном слое для валидации токенов (авторизации) реализован middleware. Он добавляется на моменте инициализации группы роутов, к которой я приписываю, какие роли имеют право на обращение к данной группе.

Сама авторизация очень примитивная. Есть просто два вида токенов: буквально userToken и adminToken. Из них уже парсится роль пользователя и передается дальше через контекст. Можно было бы реализовать это всё через JWT-токены, но я решил, что это сейчас не в приоритете

Docker-compose

Я реализовал два Docker-compose файла: один для запуска сервиса, а другой для поднятия зависимостей для тестирования. Каждая БД проверяется через healthcheck, что всё запустилось. Поэтому не должно быть проблем, когда сервер запускается быстрее БД

Graceful shutdown

Для того, чтоб не терялись данные при перезапуске сервера, реализован процесс Graceful Shutdown или же Плавное выключение. Когда сервер принимает сигнал от OS о его выключении, сервер сразу не перестает работать а выключается по такому алгоритму:

  1. Прекращается получение входящих http запросов
  2. Обработка всех полученных ранее запросов
  3. После отработки последнего запроса закрытие всех подключений к базе данных
  4. Выключение сервера

Выводы

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

Жаль, что не всё успел, но мне настолько понравился проект, что и после сдачи я продолжу его дорабатывать. Реализую все пункты из задания, сделаю нормальную авторизацию через JWT токены. Ну и ещё ряд моментов, которые я записал себе в блокнотик ахах

Надеюсь на дальнейшее сотрудничество! Буду очень рад фидбеку)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published