Тестовое задание Advertising
Содержание
- Описание задачи
- Реализация
- Архитектура
- Endpoints
- Запуск
- Тестирование
- Документация
- Нагрузочное тестирование
- Примеры
Описание задачи
Необходимо создать сервис для хранения и подачи объявлений. Объявления должны храниться в базе данных. Сервис должен предоставлять API, работающее поверх HTTP в формате JSON.
Метод получения списка объявлений
- Пагинация: на одной странице должно присутствовать 10 объявлений;
- Cортировки: по цене (возрастание/убывание) и по дате создания (возрастание/убывание);
- Поля в ответе: название объявления, ссылка на главное фото (первое в списке), цена.
Метод получения конкретного объявления
- Обязательные поля в ответе: название объявления, цена, ссылка на главное фото;
- Опциональные поля (можно запросить, передав параметр fields): описание, ссылки на все фото.
Метод создания объявления:
- Принимает все вышеперечисленные поля: название, описание, несколько ссылок на фотографии (сами фото загружать никуда не требуется), цена;
- Возвращает ID созданного объявления и код результата (ошибка или успех).
Реализация
- Следование дизайну REST API.
- Подход "Чистой Архитектуры" и техника внедрения зависимости.
- Работа с фреймворком echo.
- Работа с БД Postgres с использованием библиотеки sqlx и написанием SQL запросов.
- Конфигурация приложения - библиотека viper.
- Реализация Graceful Shutdown.
- Запуск из Docker.
- Юнит-тестирование уровней обработчиков, бизнес-логики и взаимодействия с БД классическим способом и с помощью моков - библиотеки testify, mock.
- Сквозное (E2E) тестирование - BDD фреймворк goconvey.
- Проверка кода на соответствие стандартам с помощью линтера - утилита golangci-lint
- Автоматическое создание документации с помощью Swagger 2.0 - библиотека echo-swagger.
- Непрерывная интеграция - сборка приложения, проверка линтером и запуск тестов в Github action.
Структура проекта:
.
├── pkg
│ ├── error_message // сообщения об ошибках
│ ├── model // основные структуры
│ ├── handler // обработчики запросов
│ ├── service // бизнес-логика
│ └── repository // взаимодействие с БД
├── cmd // точка входа в приложение
├── migrations // SQL файлы с миграциями
├── scripts // SQL файлы с тестовыми данными
├── configs // файлы конфигурации
├── test // инициализация тестовой БД
└── e2e_test.go // сквозной тест
Архитектура
Приложение имеет 3 основных слоя, реализованных в отдельных пакетах.
- Repository - слой взаимодействия с БД. Методы этого слоя принимают данные от Service и выполняют запросы к БД.
- Service - слой бизнес-логики. Методы этого слоя принимают данные от Handler и применяют к ним бизнес-правила для достижения цели варианта использования.
- Handler - слой обработчиков запросов. Содержит методы-обработчики для endpoints.
Пакет Model содержит структуры сущностей, используемых остальными слоями.
Endpoints
- GET /api/adverts - получение списка объявлений
- Параметры запроса:
- page - номер страницы,
- sort - параметры сортировки в формате [поле сортировки]_[порядок сортировки] (например price_desc).
- GET /api/adverts/:id - получение объявления по id
- Параметры запроса:
- fields - флаг, если равен True, то вернуть все поля, иначе вернуть название, ссылку на главное фото и цену.
- POST /api/adverts - создание объявления
- Тело запроса:
- title - название объявления,
- description - описание объявления,
- photos - ссылки на фотографии,
- price - цена.
Запуск
make build
make run
Если приложение запускается впервые, необходимо применить миграции к базе данных:
make migrate_up
Для миграций используется golang-migrate/migrate CLI.
Тестирование
Локальный запуск тестов:
make run_test
Для локального запуска тестов необходимо создать тестовую БД. Это можно сделать следующей командой (необходима утилита psql):
make create_test_db
Документация
Для просмотра документации Swagger необходимо запустить приложение и перейти по ссылке http://127.0.0.1:9000/swagger/index.html
Нагрузочное тестирование
Нагрузочное тестирование проведено с помощью утилиты Apache Benchmark.
Результаты представлены в файле ab_results.md
Примеры
Запросы сгенерированы из Postman для cURL.
Получение списка объявлений
1. GET для page=1 (без сортировки)
Запрос:
$ curl GET localhost:9000/api/adverts?page=1
Тело ответа:
[
{
"id": 1,
"title": "Advert 1",
"main_photo": "link1",
"price": 10000
},
{
"id": 2,
"title": "Advert 2",
"main_photo": "link1",
"price": 60000
},
{
"id": 3,
"title": "Advert 3",
"main_photo": "link1",
"price": 30000
}
]
2. GET для page=1 и sort=price_asc
Запрос:
$ curl GET localhost:9000/api/adverts?page=1&sort=price_asc
Тело ответа:
[
{
"id": 1,
"title": "Advert 1",
"main_photo": "link1",
"price": 10000
},
{
"id": 3,
"title": "Advert 3",
"main_photo": "link1",
"price": 30000
},
{
"id": 2,
"title": "Advert 2",
"main_photo": "link1",
"price": 60000
}
]
3. GET для page=1 и sort=date_desc
Запрос:
$ curl GET localhost:9000/api/adverts?page=1&sort=date_desc
Тело ответа:
[
{
"id": 3,
"title": "Advert 3",
"main_photo": "link1",
"price": 30000
},
{
"id": 2,
"title": "Advert 2",
"main_photo": "link1",
"price": 60000
},
{
"id": 1,
"title": "Advert 1",
"main_photo": "link1",
"price": 10000
}
]
Получение конкретного объявления
1. GET для id=1
Запрос:
$ curl GET localhost:9000/api/adverts/1
Тело ответа:
{
"id": 1,
"title": "Advert 1",
"photos": [
"link1"
],
"price": 10000
}
2. GET для id=1 и fields=true
Запрос:
$ curl GET localhost:9000/api/adverts/1?fields=true
Тело ответа:
{
"id": 1,
"title": "Advert 1",
"description": "Description 1",
"photos": [
"link1",
"link2",
"link3"
],
"price": 10000
}
Создание объявления
Запрос:
$ curl --location --request POST 'localhost:9000/api/adverts' \
--header 'Content-Type: application/json' \
--data-raw '{
"title": "New advert",
"description": "Description of new advert",
"photos": ["link1", "link2"],
"price": 400000
}'
Тело ответа:
{
"advert_id": 4
}