https://github.com/merrazeal/python-hexagonal-architecture-example
Payment service example built with Hexagonal Architecture in Python
https://github.com/merrazeal/python-hexagonal-architecture-example
asyncio clean-architecture ddd dependency-injection fastapi faststream hexagonal-architecture outbox-pattern ports-and-adapters python
Last synced: 10 days ago
JSON representation
Payment service example built with Hexagonal Architecture in Python
- Host: GitHub
- URL: https://github.com/merrazeal/python-hexagonal-architecture-example
- Owner: merrazeal
- Created: 2026-05-28T16:01:21.000Z (16 days ago)
- Default Branch: main
- Last Pushed: 2026-05-29T03:26:19.000Z (16 days ago)
- Last Synced: 2026-05-29T05:16:11.330Z (16 days ago)
- Topics: asyncio, clean-architecture, ddd, dependency-injection, fastapi, faststream, hexagonal-architecture, outbox-pattern, ports-and-adapters, python
- Language: Python
- Homepage:
- Size: 93.8 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Python Hexagonal Architecture Example
Практический пример модуля обработки платежей, построенного на принципах Clean Architecture (Hexagonal Architecture).
В примере показано:
- как реализовать надёжную консистентную доставку событий через Outbox pattern;
- как снижать стоимость изменений и добавления новых интеграций за счёт изоляции бизнес-логики от инфраструктуры через порты и адаптеры;
- как упростить E2E тестирование - нестабильные внешние интеграции можно подменять Fake реализациями на уровне окружения с помощью DI контейнеров;
- как безопасно обрабатывать платежи без двойных списаний через SELECT FOR UPDATE;
Сервис написан как живой код к статье о том, почему инфраструктурные детали не должны протекать в бизнес-логику, и как это решается через контракты, адаптеры и правильное разделение слоёв.
Статья: https://habr.com/ru/articles/1034758/
## Что внутри
### Стек
- **FastAPI** — REST API
- **FastStream + RabbitMQ** — асинхронная обработка сообщений (worker)
- **APScheduler** — планировщик задач (scheduler)
- **SQLAlchemy CORE (async) + PostgreSQL** — хранение данных
- **Alembic** — миграции
- **Dishka** — dependency injection container
### Структура проекта
```
src/
├── domain/ # Доменные сущности и их локальные правила (статусы, исключения, допустимые инварианты и т.д)
├── ports/ # Контракты: интерфейсы репозиториев, UoW, gateway, publisher
├── usecases/ # Бизнес-сценарии: create, get, process, dispatch
├── adapters/ # Реализации контрактов: SQLAlchemy, RabbitMQ, HTTP, payment gateway
├── handlers/ # Точки входа: REST routes, FastStream tasks, cron jobs
└── boot/
├── dev/ # DI-контейнер и entrypoint'ы для разработки (внешние Stub-реализации)
└── test/ # DI-контейнер и entrypoint'ы для тестов (внешние Fake-реализации)
```
### Полный flow платежа
```
POST /api/v1/payments
│
▼
CreatePaymentUseCase
├── сохраняет Payment в Postgres
└── сохраняет запись в Outbox
│
▼ (каждые N секунд)
Scheduler → DispatchPaymentEventsUseCase
└── публикует событие payment.created → RabbitMQ
│
▼
Worker → ProcessPaymentUseCase
├── списывает средства через IPaymentGateway
├── обновляет статус Payment (SUCCEEDED / FAILED)
└── доставляет webhook на webhook_url
```
Outbox pattern гарантирует доставку событий в RabbitMQ даже при падении сервиса между созданием платежа и публикацией.
---
## Запуск для разработки
```bash
docker compose -f docker-compose.dev.yml up --build
```
API будет доступен на `http://localhost:9090`.
Пример запроса:
```bash
curl -X POST http://localhost:9090/api/v1/payments \
-H "Content-Type: application/json" \
-H "X-API-Key: dev-secret-api-key" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": "100.00",
"currency": "RUB",
"description": "Order #1",
"metadata": {"order_id": 1},
"webhook_url": "https://example.com/webhook"
}'
```
---
## Тесты
### Юнит-тесты (необходимо настроить локальное окружение)
Тестируют use cases в полной изоляции — без базы данных, без брокера, без HTTP. Все зависимости заменены hand-rolled mock'ами.
```bash
make unit
```
### E2E-тесты
Поднимают полный стек в контейнерах: реальный PostgreSQL, реальный RabbitMQ, web + worker + scheduler на Fake-реализациях внешних вызовов.
Тест создаёт платёж через REST API, ждёт пока scheduler опубликует событие в RabbitMQ, worker его обработает и обновит статус в базе, затем проверяет финальное состояние через GET.
```bash
make e2e
```
---