Задание пока что не прикрепляю, чтобы случайно не распространить решение.
Я не согласен со следующим высказыванием в задании:
События как таковые хранить не требуется, мы будем работать только с их идентификаторами.
Если события будут храниться в ставках, то очень просто получить несогласованное состояние. Например, событие N завершилось и на это событие было пару ставок. Вызван метод на обновлении статуса события N, в этот момент меняются статусы у ставок. До завершения метода на обновления статуса вызывается метод на создание еще одной ставки на событие N. В итоге, пара ставок имеют конечные статусы (выиграла, проиграла), а одна ставка создалась позднее и имеет неконечный статус (ещё не сыграла). Я предлагаю работать с событием, как с обычной сущностью и добавить метод создания, что дополнительно упростит архитектуру и понимание предметной области. Я вижу процесс следующим образом:
- Создание события;
- Создание ставок на события;
- Завершение события, сменой статуса;
Так же я предлагаю изменить метод создания ставки POST /bets
. Метод работает с деньгами, поэтому важно, чтобы из-за
проблем (например, сетевых) не случилось создание двух одинаковых ставок. Я предлагаю реализовать идемпотентность
следующим образом: пусть клиент на своей стороне создает идентификатор ставки (UUID) и отправляет его в запрос. В таком
случае, приложение просто убедится в том, что такая ставка уже есть и не будет создавать ее повторно. Так же не
требуется прикреплять в ответ идентификатор ставки, потому что клиент и так его знает.
Дополнительные нефункциональные требования:
- За какое время запрос должен быть обработан на серверной стороне?
запрос должен быть обработан не более чем за 100мс.
- Какое количество событий и ставок планируется хранить?
кол-во событий — порядка 10000.
Библиотеки и технологии подобраны таким образом, чтобы продемонстрировать знания требующиеся в вакансии.
- Python 3.11
- FastAPI
- SQLAlchemy ORM
- Alembic
- structlog
- pytest
- PostgreSQL
- Docker
- API
- Миграции для базы данных
- Логирование
- Тестирование
- Code-style
- Сборка приложения
Сервис позволяет создавать события, на которые можно делать ставки, и в дальнейшем отслеживать их статус.
На события можно делать ставки. Событие может находиться в одном из 3 статусов:
- Ещё не сыграла (
PENDING
); - Выиграла (
WIN
); - Проиграла (
LOSE
);
Сделать ставку не получиться, если событие находиться в одном из конечных статусов (WIN
, LOSE
).
Можно делать сколь угодно ставок на одно и то же событие. Важно, чтобы событие было в неконечном
статусе (WIN
, LOSE
).
Сумма ставки обязательно должна быть положительным числом.
Интерфейс приложения (API) и само приложение (Bets) отделены, чтобы в будущем оставить возможность реализовать дополнительные интерфейсы.
Приложение разделено на слои (Domain
, Application
, Infrastructure
). Такое разделение дает гибкость приложению, что
положительно сказывается на разработке будущих компонентов. Так же такое разделение упрощает процесс тестирования,
потому что сохраняется минимальна связанность компонентов.
Dockerfile и docker-compose файлы лежат в директории deployment.
Для запуска нужно выполнить команду ниже:
docker compose --project-name test --file deployment/docker-compose.yml up
Важно! После того как база данных поднялась, требуется применить миграции.
Переменная окружения | Описание | Значение по умолчанию |
---|---|---|
DB_DATABASE | Название базы данных | bet-maker |
DB_USER | Имя пользователя базы данных | test |
DB_PASSWORD | Пароль пользователя базы данных | test |
DB_PORT | Порт базы данных | 6432 |
APPLICATION_PORT | Порт приложения | 80 |
Установить переменные окружения в терминале и выполнить скрипт для применения миграций.
В моем случае переменная DB_DSN
была postgresql+asyncpg://test:test@localhost:6432/bet-maker
.
export DB_DSN=<ПОДКЛЮЧЕНИЕ К БАЗЕ ДАННЫХ>
alembic upgrade head
Находится по адресу - http://localhost/
.
Документация Swagger находится по адресу - http://localhost/docs
.
Создать событие. Данный метод является идемпотентным.
Тело запроса: Тело запроса:
{
"event_id": "3fa85f64-5717-4562-b3fc-2c963f66afa5"
}
event_id
- Идентификатор события в формате UUID.
Ответ:
- В случае корректного создания вернется пустой ответ с HTTP статусом 201.
- В случае некорректно заданного
event_id
вернется ответ с HTTP статусом 422 и описанием ошибки.
Изменить статус события.
Тело запроса: Тело запроса:
{
"status": "WIN"
}
status
- Возможен один из вариантов статусаWIN
илиLOSE
.
Ответ:
- В случае корректного запроса вернется пустой ответ с HTTP статусом 200.
- В случае некорректно выбранного статуса
status
вернется ответ с HTTP статусом 422 и описанием ошибки. - В случае несуществующего события с
event_id
вернется ответ с HTTP статусом 404.
Сделать ставку. Данный метод является идемпотентным.
Тело запроса:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa3",
"event_id": "3fa85f64-5717-4562-b3fc-2c963f66afa5",
"amount": 1.11
}
id
- Идентификатор ставки в формате UUID.event_id
- Идентификатор события в формате UUID.amount
- Сумма ставки должна строго положительным числом с двумя знаками после запятой.
Ответ:
- В случае корректного запроса вернется пустой ответ с HTTP статусом 201.
- Если выбранное событие не существует, то вернется ответ с HTTP статусом 400 и кодом ошибки -
EVENT_NOT_FOUND
. - Если выбранное событие имеет финальный статус, то вернется ответ с HTTP статусом 400 и кодом
ошибки -
EVENT_STATUS_ALREADY_IS_FINAL
. - В случае некорректно заданного тела запроса вернется ответ с HTTP статусом 422 и описанием ошибки.
Получить информацию о существующих ставках.
- В случае корректного запроса вернется ответ с HTTP статусом 201. Тело ответа:
{
"bets": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amount": 0,
"created_at": "2024-05-10T07:47:38.723Z",
"event": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "PENDING",
"created_at": "2024-05-10T07:47:38.723Z"
}
}
]
}
id
- Идентификатор ставки в формате UUID.amount
- Сумма ставки должна строго положительным числом с двумя знаками после запятой.created_at
- Дата создания ставки.event:id
- Идентификатор события в формате UUID.event:status
- Возможен один из вариантов статусаWIN
,LOSE
,PENDING
.event:created_at
- Дата создания события.
Подготовка локального окружения:
- Установка всех зависимостей
pip install -r requirements.txt
pip install -r requirements-test.txt
pip install -r requirements-dev.txt
- Установка pre-commit хуков
pre-commit install
Перед каждым git commit pre-commit выполняет следующие действия:
- Удаление ненужных пробелов
- Добавление пустой строки в конце файла
- Запуск code-style проверок
- Запуск тестов
В случае неуспешного прохождения проверок коммит не будет создан.
Конфигурация pre-commit находится в файле .pre-commit-config.yaml.
Чтобы выполнить проверку вручную можно вызвать команду:
pre-commit run --all-files
Проверка code-style происходит с помощью утилиты Ruff
.
Конфигурация находится в pyproject.toml. Подробнее про настройку можно прочесть
в документации Ruff.
Для запуска проверки code-style требуется выполнить команду:
ruff check .
Тесты написаны на базе фреймворка pytest
. Конфигурация находится в pyproject.toml. Тесты расположены
в директории tests.
Для запуска тестов требуется выполнить команду:
python -m pytest