Материал

FastAPI project

Самостоятельное задание: сервис кошельков на FastAPI и SQLAlchemy

Создайте новый публичный репозиторий или отдельную папку проекта. Задание выполняется с нуля и не зависит от веток или результатов предыдущих домашних работ.

Контекст сервиса

Сервис отвечает только за домен кошельков: хранение баланса и переводы между кошельками. Пользователь представлен внешним user_uid: UUID; собственной сущности пользователя нет. Аутентификация и авторизация не входят в это учебное задание. Локально сервис запускается как самостоятельное приложение. Для production отдельно потребовались бы trust model, защита от обхода gateway и проверка прав на операцию.

Минимальное окружение: Python, FastAPI, SQLAlchemy 2.x, Alembic, PostgreSQL, pytest и Docker Compose. Инструкции установки, запуска миграций и тестов должны находиться в README.

Задачи:

  1. SQLAlchemy.

Проработать архитектуру моделей сервиса по переводу денег. Нужно сделать сущность кошелек и сущность “транзакция”/перевод. Кошельки привязаны к пользователям (user_uid: UUID, uuid из другой системы).
Проработать ограничения реализации.

Чем пользуемся:

  1. Лекция :)
  2. SQLAlchemy модели https://docs.sqlalchemy.org/en/20/orm/quickstart.html#declare-models
  3. Alembic migrations.
  4. https://alembic.sqlalchemy.org/en/latest/tutorial.html (doc)
  5. https://habr.com/ru/articles/585228/ (RU) Что сделать:
  6. Составить список возможных вопросов и предложения решения проблем
  7. Например: «Может ли баланс кошелька уходить в минус?» — «Нет». Тогда рассматриваем ограничение на уровне БД и корректную обработку конкурентных операций.
  8. Все непонятные вещи уточнить у нас (тренируемся формировать требования)
  9. Реализовать сущности и миграцию.
  10. Обдумать, какие нужны индексы и ограничения.
  11. Добавить миграцию Alembic. Полезно изучить:
  12. https://habr.com/ru/articles/735606/
  13. https://habr.com/ru/articles/580866/
  14. FastAPI handlers. Реализовать ручки получения баланса кошелька (отдельно по user_uid, отдельно по wallet_id). Реализовать ручку с переводом денег с одного кошелька на другой
  15. Реализовать ручки
  16. GET /wallet/{id} — получение баланса кошелька. Учесть валидацию; если кошелёк отсутствует, вернуть 404 Not Found.
  17. GET /users/{user_uid}/wallet
  18. POST или PUT /wallet/transaction — обосновать выбор метода, URI ресурса и модель идемпотентности.
  19. Покрыть тестами все сценарии
  20. Подумать и реализовать идемпотентный перевод денег
  21. Подумать и реализовать конкурентный перевод денег Полезно изучить:
  22. Кастом модели на pydantic GitHub - zhanymkanov/fastapi-best-practices: FastAPI Best Practices and Conventions we used at our startup
  23. Repository паттерн для FastAPI
  24. Fast API — Repository Pattern and Service Layer.
  25. Архитектура fast api приложений. Внедрение зависимостей
  26. Clean Architecture глазами Python-разработчика
  27. Тестирование ручек
  28. Testing - FastAPI
  29. Блокировки строк и уровни изоляции для конкурентных изменений
  30. Лекция 🙂
  31. PostgreSQL: explicit locking
  32. SQLAlchemy: SELECT … FOR UPDATE Дополнительные задания:
  33. Добавить Redis в Docker Compose.
  34. Добавить кэширование GET-операции получения баланса и продумать инвалидацию кэша при переводе.

Примеры сервисов

Пример реализации простого сервиса с ручками:

Критерии готовности

  • проект с нуля запускается по README и поднимает PostgreSQL через Docker Compose;
  • Alembic применяет миграции к пустой базе;
  • модели кошелька и перевода содержат обоснованные типы, constraints, indexes и связи;
  • API возвращает корректные 2xx, 404 и 409/422 для выбранных ошибочных сценариев;
  • перевод выполняется в одной транзакции и не допускает отрицательного баланса при конкурентных запросах;
  • повтор запроса с тем же idempotency key не создаёт второй перевод;
  • тесты проверяют успешный перевод, недостаток средств, отсутствующий кошелёк, повтор idempotency key и конкурентное списание;
  • секреты и локальный .env не находятся в репозитории; присутствует безопасный .env.example;
  • дополнительный Redis-кэш, если реализован, не является источником истины и инвалидируется после перевода.