Высоконагруженный асинхронный микросервис для управления мультивалютными кошельками, проведения транзакций и конвертации валют. Проект спроектирован с упором на отказоустойчивость, финансовую точность и защиту от классических проблем распределенных систем (Race Conditions, Deadlocks, Double Spending).
Примечание: Этот документ описывает не только процесс запуска, но и внутреннюю архитектуру, принятые паттерны проектирования и механизмы обеспечения консистентности данных.
- Архитектура системы
- Ключевые паттерны и бизнес-логика
- Стек технологий
- Инфраструктура и Развертывание (Docker)
- Тестирование
- Быстрый старт
Система разделена на 4 независимых компонента, общающихся в изолированной сети Docker:
- API Gateway / Core Service (FastAPI): Принимает запросы пользователей, валидирует данные, взаимодействует с БД и отправляет задачи в очередь. Работает в полностью асинхронном режиме.
- Relational Database (PostgreSQL): Хранит состояние кошельков, пользователей и историю транзакций. Является единым источником истины (Single Source of Truth), обеспечивает выполнение ACID-требований.
- In-Memory Cache & Message Broker (Redis): Выполняет три роли:
- Брокер сообщений для фоновых задач.
- Хранилище распределенных блокировок (Distributed Locks).
- Кэш для внешних данных (курсы валют от API ЦБ РФ с TTL = 1 час).
- Background Worker (Taskiq): Асинхронно обрабатывает «тяжелые» или отложенные задачи (например, финализацию транзакций и отправку уведомлений), снимая нагрузку с основного API.
В распределенных сетях клиент может отправить один и тот же запрос дважды (из-за потери ответа или лага сети). Система защищена от двойного списания средств:
- Каждый запрос на перевод должен содержать уникальный
idempotency_key. - При попытке вставить дубликат ключа база данных выбрасывает
IntegrityError. - Сервис перехватывает ошибку, откатывает транзакцию (
db.rollback()) и извлекает результат предыдущего успешного запроса по этому ключу. - Защита от коллизий: Если ключ совпадает, но параметры перевода (сумма, получатель) отличаются, транзакция отклоняется с ошибкой
Data conflict.
При встречных переводах (Пользователь А переводит Пользователю Б, и одновременно Пользователь Б переводит Пользователю А) транзакции в БД могут заблокировать друг друга.
- Решение: Применен паттерн Resource Ordering (Упорядочивание ресурсов).
- При выполнении
SELECT ... FOR UPDATEсистема всегда сначала блокирует кошелек с наименьшим ID, а затем с наибольшим. Это гарантирует, что все параллельные транзакции выстраиваются в единую однонаправленную очередь ожиданий.
Чтобы исключить ситуацию (Race Condition), когда несколько инстансов воркера пытаются обработать одну и ту же транзакцию:
- Используется механизм Distributed Lock на базе Redis (
SET key value NX EX 300). - Если воркер видит, что блокировка занята, он немедленно завершает работу.
- Установлен TTL (Time-To-Live) в 300 секунд. Если воркер падает (Out Of Memory, Kernel Panic) во время обработки, блокировка автоматически снимется, и транзакция не зависнет в системе навсегда.
- При нормальном завершении блокировка гарантированно снимается в блоке
finally.
- Использование чисел с плавающей точкой (
float) недопустимо в финансовых операциях из-за проблем с представлением дробей в двоичной системе. - Все вычисления, включая конвертацию валют по курсу ЦБ, выполняются с использованием типа
Decimal. - Применяется жесткое квантование (
.quantize(Decimal("0.01"))), что исключает появление "микро-копеек" и расхождение балансов.
- Runtime: Python 3.13+,
- Backend Framework: FastAPI, Uvicorn (ASGI server)
- Data validation & Settings: Pydantic v2 (включая pydantic-settings и валидацию email)
- Database Layer: PostgreSQL 15-aplhine, SQLAlchemy 2.0 (asyncio)
- Drivers: asynpg, psycopg2-binary (для системных утилит)
- Mirgations: Alembic
- Async Task Queue: Taskiq + Taskiq-Redis, Redis 7-aplhine
- Security & Auth:
- Tokens: JWT (через PyJWT и python-jose),
- Hashing: Passlib + Bcrypt (алгоритм Bcrypt)
- Multipart: python-multipart для обработки сложных запросов
- Observability:
- Logging: Python Standart Logging (RotatingFileHandler)
- Error Tracking: Sentry
- Deployment & Testing:
- Testing: Pytest, pytest-asyncio, pytest-mock, httpx (интеграционные тесты)
- Code Quality: Ruff (linter), Black (formatter), Mypy (static type checking), isort
Проект полностью контейнеризирован и готов к работе в любой среде.
- Multi-stage build:
Dockerfileоптимизирован. Сборка зависимостей и финальный образ разделены, что делает контейнер легким и безопасным. - Least Privilege: Приложение внутри контейнера запускается от имени пользователя без прав root (
appuser). - Healthchecks & Wait-for-it: В
docker-compose.ymlнастроена строгая последовательность запуска. API и Worker стартуют только после того, как PostgreSQL и Redis сообщат о готовности принимать соединения. - Auto-Migrations: При старте API контейнер автоматически применяет свежие миграции (
alembic upgrade head).
Проект покрыт unit- и интеграционными тестами (tests/) с акцентом на тестирование краевых случаев (Edge Cases):
- Dependency Injection: В тестах реальная база данных подменяется на моки (Mock Objects) через
app.dependency_overrides. Это позволяет прогонять сотни тестов за доли секунды без поднятия СУБД. - SQLAlchemy Mocks: Написана кастомная фикстура
mock_refresh, имитирующая поведение БД послеcommit()(генерация ID и дефолтных значений). - Сценарии:
- Аутентификация и Жизненный цикл (test_api.py):
- Регистрация новых пользователей и валидация уникальности данных.
- Проверка успешного Login-процесса и выдачи JWT-токенов.
- Инициализация первого кошелька пользователя и привязка к профилю.
- Бизнес-логика транзакций (test_transactions.py):
- Atomic Transfers: Успешный перевод между кошельками в одной валюте.
- Currency Exchange: Корректность кросс-валютных переводов с использованием динамических курсов.
- Verification: Проверка автоматического вызова фоновых задач (Taskiq) для финализации транзакций.
- Работа Воркера и Конкурентность (test_worker.py):
- Happy Path: Полный цикл завершения транзакции фоновым процессом.
- Race Condition Protection: Проверка механизма предотвращения Double Spending. Если один воркер захватил замок в Redis, второй обязан прервать операцию.
- Обработка ошибок и Edge Cases (test_errors.py):
- Валидация сущностей: Запрет создания дубликатов кошельков или кошельков с несуществующими валютами (USD, RUB, EUR).
- Безопасность: Проверка защиты эндпоинтов (запрет доступа неавторизованным пользователям).
- Финансовые ограничения:
- Блокировка транзакций при недостаточном балансе.
- Запрет перевода средств «самому себе».
- Обработка попыток перевода с несуществующих счетов.
- Reliability: Проверка механизма идемпотентности. Повторный запрос с тем же idempotency_key возвращает старый результат, не создавая новую транзакцию и не списывая средства дважды.
- Аутентификация и Жизненный цикл (test_api.py):
1. Клонирование репозитория и настройка окружения:
git clone https://github.com/whxtelyy/payment-distribution-service.git
cd payment-distribution-service
cp .env.example .env
# Отредактируйте .env файл, указав свои креды (если необходимо).В файле .env укажите базовые доступы к БД и SENTRY_DSN (опционально, для работы мониторинга и сбора метрик).
2. Запуск через Docker Compose:
docker-compose up --buildКонтейнеры соберутся, БД поднимется, пройдут миграции и запустятся тесты перед стартом сервера. Важно: контейнер api настроен на жесткую проверку: сервер Uvicorn запустится только в том случае, если pytest успешно пройдет все интеграционные тесты внутри изолированной среды.
3. Использование API:
- Интерактивная документация Swagger доступна по адресу:
http://localhost:8000/docs - Логи транзакций и работы воркера можно найти в директории
app/logs/transaction.log.