Skip to content

Repository files navigation

Wallet API

REST API для управления кошельками с поддержкой идемпотентности и защитой от race conditions.

Быстрый старт

  1. Скопируйте .env.example в .env и при необходимости измените доступы к БД:
cp .env.example .env

.env содержит секреты и не попадает в git (см. .gitignore).

  1. Запустите приложение:
docker-compose up -d

Приложение будет доступно на http://localhost:8000.

Эндпоинты

Метод Путь Idempotency-Key Описание
GET /health Healthcheck
POST /api/v1/wallets Создать кошелёк
POST /api/v1/wallets/{id}/deposit Пополнить кошелёк
POST /api/v1/wallets/transfer Перевести средства
GET /api/v1/wallets/{id}?limit=50 Баланс и история операций

Примеры запросов

# Создать кошелёк
curl -X POST http://localhost:8000/api/v1/wallets \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{"metadata": {"owner": "Alice"}}'

# Пополнить кошелёк
curl -X POST http://localhost:8000/api/v1/wallets/<wallet_id>/deposit \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440001" \
  -d '{"amount": "100.00"}'

# Перевести средства
curl -X POST http://localhost:8000/api/v1/wallets/transfer \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440002" \
  -d '{"from_wallet_id": "<id>", "to_wallet_id": "<id>", "amount": "30.00"}'

# Получить баланс и историю
curl http://localhost:8000/api/v1/wallets/<wallet_id>?limit=10

Запуск тестов

# Убедитесь, что PostgreSQL запущен и доступен
docker-compose up -d db

# Скопируйте .env (если ещё не сделали этого)
cp .env.example .env

# Установите зависимости
pip install -r requirements.txt

# Запустите тесты
pytest -v

Архитектурные решения

1. Защита от race conditions

Проблема: два одновременных запроса на перевод с одного кошелька могут прочитать баланс 100, проверить, что 100 >= 100, и оба выполнить списание, уйдя в минус.

Решение: пессимистичная блокировка строк (SELECT ... FOR UPDATE).

  • При операциях с балансом (deposit, transfer) все затрагиваемые кошельки блокируются в одной транзакции с помощью SELECT ... FOR UPDATE.
  • Кошельки блокируются в порядке ORDER BY id — это гарантирует отсутствие deadlock'ов.
  • Второй запрос ждёт завершения первого, видит уже обновлённый баланс и корректно обрабатывает ситуацию (недостаточно средств).

2. Идемпотентность

Проблема: клиент отправляет перевод, не дожидается ответа из-за обрыва сети и отправляет тот же запрос снова. Деньги могут списаться дважды.

Решение: заголовок Idempotency-Key.

  • Все mutation-эндпоинты требуют заголовка Idempotency-Key.
  • Перед выполнением операции проверяется, не было ли уже обработано такого же ключа.
  • Если ключ уже есть и тело запроса совпадает — возвращается закешированный ответ.
  • Если ключ уже есть, но тело отличается — 409 Conflict.
  • Idempotency-ключ сохраняется в той же транзакции, что и операция. Если два concurrent запроса с одинаковым ключом, UNIQUE-constraint базы данных гарантирует, что операция выполнится только один раз.

3. Точность вычислений

  • Все денежные суммы хранятся в NUMERIC(20, 2) в PostgreSQL.
  • В Python используется Decimal.
  • В API суммы передаются строками (например, "100.00"), чтобы избежать потери точности при сериализации JSON.

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

  • FastAPI — асинхронный веб-фреймворк.
  • SQLAlchemy 2.0 (async) — ORM.
  • asyncpg — драйвер PostgreSQL.
  • Alembic — миграции.
  • Pytest + pytest-asyncio + httpx — тестирование.
  • Docker Compose — инфраструктура.

5. Структура кода

app/
├── models/        # ORM-модели (Wallet, Transaction, IdempotencyKey)
├── schemas/       # Pydantic-схемы (запросы/ответы)
├── repositories/  # Доступ к данным (изоляция SQL)
├── services/      # Бизнес-логика (WalletService, IdempotencyService)
├── routers/       # FastAPI-эндпоинты
├── main.py        # Точка входа
├── database.py    # Engine, session, Base
├── config.py      # Конфигурация
└── exceptions.py  # Кастомные исключения

Слои:

  • Роутер — валидация запроса, проверка идемпотентности, вызов сервиса.
  • Сервис — бизнес-логика, управление транзакциями.
  • Репозиторий — SQL-запросы.

6. Обработка ошибок

Ситуация Статус
Кошелёк не найден 404
Недостаточно средств 400
Невалидная сумма (<= 0) 422
Idempotency-Key отсутствует 422
Idempotency-Key + другое тело 409
Перевод самому себе 400

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages