Самостоятельное задание: сервис кошельков на FastAPI и SQLAlchemy
Создайте новый публичный репозиторий или отдельную папку проекта. Задание выполняется с нуля и не зависит от веток или результатов предыдущих домашних работ.
Контекст сервиса
Сервис отвечает только за домен кошельков: хранение баланса и переводы между кошельками. Пользователь представлен внешним user_uid: UUID; собственной сущности пользователя нет. Аутентификация и авторизация не входят в это учебное задание. Локально сервис запускается как самостоятельное приложение. Для production отдельно потребовались бы trust model, защита от обхода gateway и проверка прав на операцию.
Минимальное окружение: Python, FastAPI, SQLAlchemy 2.x, Alembic, PostgreSQL, pytest и Docker Compose. Инструкции установки, запуска миграций и тестов должны находиться в README.
Задачи:
- SQLAlchemy.
Проработать архитектуру моделей сервиса по переводу денег. Нужно сделать сущность кошелек и сущность “транзакция”/перевод. Кошельки привязаны к пользователям (user_uid: UUID, uuid из другой системы).
Проработать ограничения реализации.
Чем пользуемся:
- Лекция :)
- SQLAlchemy модели https://docs.sqlalchemy.org/en/20/orm/quickstart.html#declare-models
- Alembic migrations.
- https://alembic.sqlalchemy.org/en/latest/tutorial.html (doc)
- https://habr.com/ru/articles/585228/ (RU) Что сделать:
- Составить список возможных вопросов и предложения решения проблем
- Например: «Может ли баланс кошелька уходить в минус?» — «Нет». Тогда рассматриваем ограничение на уровне БД и корректную обработку конкурентных операций.
- Все непонятные вещи уточнить у нас (тренируемся формировать требования)
- Реализовать сущности и миграцию.
- Обдумать, какие нужны индексы и ограничения.
- Добавить миграцию Alembic. Полезно изучить:
- https://habr.com/ru/articles/735606/
- https://habr.com/ru/articles/580866/
- FastAPI handlers. Реализовать ручки получения баланса кошелька (отдельно по user_uid, отдельно по wallet_id). Реализовать ручку с переводом денег с одного кошелька на другой
- Реализовать ручки
GET /wallet/{id}— получение баланса кошелька. Учесть валидацию; если кошелёк отсутствует, вернуть404 Not Found.- GET /users/{user_uid}/wallet
POSTилиPUT /wallet/transaction— обосновать выбор метода, URI ресурса и модель идемпотентности.- Покрыть тестами все сценарии
- Подумать и реализовать идемпотентный перевод денег
- Подумать и реализовать конкурентный перевод денег Полезно изучить:
- Кастом модели на pydantic GitHub - zhanymkanov/fastapi-best-practices: FastAPI Best Practices and Conventions we used at our startup
- Repository паттерн для FastAPI
- Fast API — Repository Pattern and Service Layer.
- Архитектура fast api приложений. Внедрение зависимостей
- Clean Architecture глазами Python-разработчика
- Тестирование ручек
- Testing - FastAPI
- Блокировки строк и уровни изоляции для конкурентных изменений
- Лекция 🙂
- PostgreSQL: explicit locking
- SQLAlchemy: SELECT … FOR UPDATE Дополнительные задания:
- Добавить Redis в Docker Compose.
- Добавить кэширование GET-операции получения баланса и продумать инвалидацию кэша при переводе.
Примеры сервисов
Пример реализации простого сервиса с ручками:
Критерии готовности
- проект с нуля запускается по README и поднимает PostgreSQL через Docker Compose;
- Alembic применяет миграции к пустой базе;
- модели кошелька и перевода содержат обоснованные типы, constraints, indexes и связи;
- API возвращает корректные
2xx,404и409/422для выбранных ошибочных сценариев; - перевод выполняется в одной транзакции и не допускает отрицательного баланса при конкурентных запросах;
- повтор запроса с тем же idempotency key не создаёт второй перевод;
- тесты проверяют успешный перевод, недостаток средств, отсутствующий кошелёк, повтор idempotency key и конкурентное списание;
- секреты и локальный
.envне находятся в репозитории; присутствует безопасный.env.example; - дополнительный Redis-кэш, если реализован, не является источником истины и инвалидируется после перевода.