REST API приложение для справочника организаций, зданий и видов деятельности, построенное на принципах Domain-Driven Design. Работа с геоданными через PostGIS
http://217.25.90.217:8000/api/docs - Live
Приложение предоставляет API для управления каталогом организаций с поддержкой:
- 🔍 Поиск организаций по зданию, виду деятельности, названию
- 📍 Геопоиск по координатам (радиус и прямоугольная область)
- 🌳 Иерархическая классификация видов деятельности (до 3 уровней вложенности)
- 🏢 Управление зданиями с адресами и координатами
- 🔐 Авторизация через JWT токены и API ключи
- 👤 Управление пользователями (регистрация, аутентификация)
- 🔑 Генерация API ключей для доступа к защищенным эндпоинтам
- Python 3.13 — язык программирования
- FastAPI — веб-фреймворк для REST API
- Pydantic — валидация данных и настройки
- PostgreSQL - база данных
- PostGIS — расширение PostgreSQL для геоданных
- SQLAlchemy 2.0 — ORM для работы с БД
- GeoAlchemy 2 - плагин для работы с PostGIS в SQLAlchemy
- Alembic — миграции базы данных
- asyncpg — асинхронный драйвер PostgreSQL
- pytest — тестирование
- ruff — линтер и форматтер
- isort — сортировка импортов
- pre-commit — хуки для проверки кода
- faker — генерация тестовых данных
- bcrypt — хеширование паролей
- authx — JWT аутентификация
- DDD (Domain-Driven Design) — проектирование на основе предметной области
- CQRS — разделение команд и запросов через Mediator
- Repository Pattern — абстракция работы с данными
- Dependency Injection — через punq контейнер
- Docker — контейнеризация
- PostgreSQL — основная база данных
- uvicorn — ASGI сервер
- ELK Stack — сбор, обработка и визуализация логов
- Elasticsearch — хранилище и поиск по логам
- Logstash — сбор и обработка логов
- Kibana — визуализация и анализ логов
-
Клонируйте репозиторий:
git clone <repository-url> cd organization_catalog_ddd
-
Настройте переменные окружения:
cp .env.example .env # Отредактируйте .env при необходимости -
Запустите приложение:
make all
-
Примените миграции:
make migrate
-
Проверьте работу:
- API документация: http://localhost:8000/api/docs
- Healthcheck: http://localhost:8000/healthcheck
| Команда | Описание |
|---|---|
make all |
Запуск всех сервисов (приложение + хранилища) |
make all-down |
Остановка всех сервисов |
make migrate |
Применение миграций (upgrade head) |
make migrations |
Создание новой миграции (autogenerate) |
make test |
Запуск тестов |
| Команда | Описание |
|---|---|
make app-logs |
Просмотр логов приложения |
make app-shell |
Подключение к shell контейнера приложения |
make app-down |
Остановка приложения |
| Команда | Описание |
|---|---|
make storages |
Запуск хранилищ (PostgreSQL) |
make storages-down |
Остановка хранилищ |
make storages-logs |
Просмотр логов хранилищ |
make postgres |
Подключение к PostgreSQL через psql |
| Команда | Описание |
|---|---|
make monitoring |
Запуск мониторинга (ELK Stack) |
make monitoring-down |
Остановка мониторинга |
make monitoring-logs |
Просмотр логов всех сервисов мониторинга |
make monitoring-restart |
Перезапуск мониторинга |
make elasticsearch-logs |
Просмотр логов Elasticsearch |
make logstash-logs |
Просмотр логов Logstash |
make kibana-logs |
Просмотр логов Kibana |
| Команда | Описание |
|---|---|
make precommit |
Запуск pre-commit проверок для всех файлов |
Проект построен на принципах Domain-Driven Design и разделен на четыре основных слоя:
Бизнес-логика и правила предметной области
domain/
├── base/ # Базовые классы
│ ├── entity.py # Базовая сущность
│ ├── value_object.py # Базовый объект-значение
│ └── exceptions.py # Доменные исключения
│
├── organization/ # Контекст организаций
│ ├── entities.py # Сущности: Organization, Building, Activity
│ ├── value_objects/ # Объекты-значения
│ │ ├── activity.py
│ │ ├── building.py
│ │ └── organization.py
│ ├── services/ # Доменные сервисы
│ │ ├── activity.py
│ │ ├── building.py
│ │ └── organization.py
│ ├── interfaces/ # Интерфейсы репозиториев
│ └── exceptions.py # Исключения домена
│
└── user/ # Контекст пользователей
├── entities.py # Сущности: User, APIKey
├── value_objects/ # Объекты-значения
│ └── user.py
├── services/ # Доменные сервисы
│ ├── user.py
│ └── api_key.py
├── interfaces/ # Интерфейсы репозиториев
└── exceptions.py # Исключения домена
Ключевые сущности:
OrganizationEntity— организация с названием, телефонами, зданием и видами деятельностиBuildingEntity— здание с адресом и координатамиActivityEntity— вид деятельности с иерархией (до 3 уровней)UserEntity— пользователь с именем и захешированным паролемAPIKeyEntity— API ключ для аутентификации
Value Objects:
ActivityNameValueObject— имя деятельности (не пустое, макс. 255 символов)BuildingAddressValueObject— адрес здания (не пустой, макс. 255 символов)BuildingCoordinatesValueObject— координаты зданияOrganizationNameValueObject— название организации (не пустое)OrganizationPhoneValueObject— телефон (10-15 цифр)UsernameValueObject— имя пользователя (мин. 3 символа, макс. 255, буквы/цифры/подчеркивания)PasswordValueObject— пароль (bcrypt hash)
Бизнес-процессы и сценарии использования
application/
├── mediator.py # CQRS медиатор
├── commands/ # Команды (изменение состояния)
│ ├── activity.py
│ ├── api_key.py
│ ├── building.py
│ ├── organization.py
│ └── user.py
├── queries/ # Запросы (чтение данных)
│ ├── activity.py
│ ├── api_key.py
│ ├── building.py
│ ├── organization.py
│ └── user.py
└── exceptions/ # Исключения слоя приложения
CQRS через Mediator:
- Commands — операции записи/изменения (CreateOrganizationCommand, CreateBuildingCommand и т.д.)
- Queries — операции чтения (GetOrganizationByIdQuery, GetOrganizationsByRadiusQuery и т.д.)
- Mediator — маршрутизация запросов к соответствующим обработчикам
Технические детали и внешние зависимости
infrastructure/
├── database/ # Работа с базой данных
│ ├── main.py # Конфигурация БД и сессий
│ ├── models/ # SQLAlchemy модели
│ │ ├── activity.py
│ │ ├── building.py
│ │ ├── organization.py
│ │ └── user.py
│ ├── repositories/ # Реализации репозиториев
│ │ ├── activity.py
│ │ ├── api_key.py
│ │ ├── building.py
│ │ ├── organization.py
│ │ ├── user.py
│ │ └── dummy/ # InMemory репозитории для тестов
│ ├── converters/ # Конвертеры Entity ↔ Model
│ │ ├── activity.py
│ │ ├── building.py
│ │ ├── organization.py
│ │ └── user.py
│ ├── gateways/ # Шлюзы для работы с БД
│ │ └── postgres.py # Database класс с сессиями
│ └── migrations/ # Alembic миграции
│ └── versions/ # Файлы миграций
└── logging/ # Логирование
├── handler.py # LogstashHandler
└── logger.py # Настройка логгера
Реализации:
- Database Gateway — управление асинхронными сессиями SQLAlchemy (read-write и read-only)
- SQLAlchemy репозитории — реализация доменных интерфейсов для PostgreSQL
- InMemory репозитории — реализации для тестирования без БД
- Конвертеры — преобразование между Entity и Model
- Логирование — интеграция с ELK Stack через LogstashHandler
API endpoints и представление данных
presentation/
└── api/ # REST API endpoints
├── main.py # Создание FastAPI приложения
├── auth.py # Авторизация (JWT, API ключи)
├── dependencies.py # Зависимости для эндпоинтов
├── exceptions.py # Обработка исключений
└── v1/ # Версия API
├── activity/ # Эндпоинты видов деятельности
├── building/ # Эндпоинты зданий
├── organization/ # Эндпоинты организаций
└── user/ # Эндпоинты пользователей
Конфигурация приложения
- Настройки через Pydantic Settings
- Поддержка переменных окружения
- Валидация конфигурации при старте
Тестирование
- Юнит-тесты доменных сущностей и value objects
- Интеграционные тесты приложения (commands/queries)
- API тесты (presentation/api/)
- InMemory репозитории для изоляции тестов
После запуска доступна по адресу: http://localhost:8000/api/docs
API использует два типа авторизации:
-
JWT токены (для пользовательских эндпоинтов):
- Токены хранятся в cookies
- Access token для коротких операций
- Refresh token для обновления access token
-
API ключи (для всех остальных эндпоинтов):
- Передаются в заголовке
Authorization: Bearer <api_key> - API ключи создаются через защищенный эндпоинт
/api/v1/user/api-key - Все эндпоинты для activities, buildings, organizations защищены API ключом
- Передаются в заголовке
POST /api/v1/user/register— регистрация нового пользователяPOST /api/v1/user/login— аутентификация и получение токеновPOST /api/v1/user/token/refresh— обновление access token
POST /api/v1/user/api-key— создание API ключа для текущего пользователя
POST /api/v1/activities— создание вида деятельностиGET /api/v1/activities/{activity_id}— получение по IDGET /api/v1/activities— список с фильтрацией (name, parent_id)
POST /api/v1/buildings— создание зданияGET /api/v1/buildings/{building_id}— получение по IDGET /api/v1/buildings— список с фильтрацией (address, coordinates)
POST /api/v1/organizations— создание организацииGET /api/v1/organizations/{organization_id}— получение по IDGET /api/v1/organizations?name={name}— поиск по названиюGET /api/v1/organizations/by-address?address={address}— поиск по адресуGET /api/v1/organizations/by-activity?activity_name={name}— поиск по виду деятельностиGET /api/v1/organizations/by-radius— геопоиск по радиусуGET /api/v1/organizations/by-rectangle— геопоиск по прямоугольной области
Все ответы API возвращаются в едином формате ApiResponse:
data— данные ответаmeta— метаинформацияerrors— список ошибок (если есть)
{
"data": {},
"meta": {},
"errors": []
}Примеры ответов:
Успешный ответ (200/201):
{
"data": {
"oid": "uuid",
"name": "Название"
},
"meta": {},
"errors": []
}Ошибка валидации (400):
{
"data": {},
"meta": {},
"errors": [
{"message": "Описание ошибки"}
]
}Ошибка валидации запроса (422):
{
"data": {},
"meta": {},
"errors": [
{
"message": "body -> name: Field required",
"type": "missing",
"field": "body -> name"
}
]
}Ошибка авторизации (401):
{
"data": {},
"meta": {},
"errors": [
{"message": "API key is required"}
]
}Приложение использует ELK стек для централизованного сбора, обработки и анализа логов.
Application → LogstashHandler → Logstash → Elasticsearch → Kibana
Кастомный handler для Python logging, который отправляет логи в Logstash через TCP:
- Автоматическое переподключение при разрыве соединения
- Graceful degradation — приложение продолжает работать даже если Logstash недоступен
- Отправка логов в формате JSON
Логирование настраивается через переменные окружения в .env:
LOGSTASH_HOST=logstash # Хост Logstash (по умолчанию: logstash)
LOGSTASH_PORT=5000 # Порт Logstash (по умолчанию: 5000)
LOGSTASH_PROJECT=organization-catalog # Имя проекта для индексации-
Запустите мониторинг:
make monitoring
-
Настройте Elasticsearch для single-node кластера:
curl -X PUT "localhost:9200/api-logs/_settings" -H 'Content-Type: application/json' -d'{ "index": { "number_of_replicas": 0 } }'
-
Проверьте статус:
make monitoring-logs
-
Доступ к сервисам:
- Elasticsearch: http://localhost:9200
- Kibana: http://localhost:5601
- Logstash: localhost:5000 (TCP)
- Откройте Kibana: http://localhost:5601
- Перейдите в Discover
- Создайте index pattern:
api-logs - Просматривайте логи в реальном времени
# Логи всех сервисов мониторинга
make monitoring-logs
# Логи конкретных сервисов
make elasticsearch-logs
make logstash-logs
make kibana-logsfrom infrastructure.logging.logger import get_logger
logger = get_logger()
logger.info(
"Пользователь выполнил действие",
extra={
"user_id": user_id,
"action": "create_organization",
"organization_id": org_id,
}
)Все логи с дополнительными полями из extra автоматически попадут в Logstash и будут проиндексированы в Elasticsearch.
make monitoring-down# Все тесты
make test- Domain tests (
app/tests/domain/) — тесты доменных сущностей и value objects - Application tests (
app/tests/application/) — тесты команд и запросов - API tests (
app/tests/presentation/api/) — интеграционные тесты API
Проект настроен для автоматического деплоя на VPS через GitHub Actions.
При деплое выполняются следующие шаги:
- Проверка кода — линтинг (
ruff,isort) и тесты (pytest) - Подключение к VPS — через SSH
- Обновление кода — pull из репозитория
- Сборка и запуск — Docker Compose
- Миграции БД — автоматическое применение через Alembic
- Health check — проверка работоспособности приложения
Для работы автоматического деплоя необходимо настроить следующие секреты в GitHub:
Необходимые секреты:
VPS_SSH_PRIVATE_KEY— приватный SSH ключ для доступа к VPSVPS_HOST— IP адрес или домен VPS сервераVPS_USER— имя пользователя для SSH подключения (обычноrootилиubuntu)VPS_APP_DIR— путь к директории проекта на сервере (например,/opt/organization_catalog_ddd)VPS_APP_URL— (опционально) URL приложения для health check (например,http://your-domain.com)
Подробные инструкции по настройке SSH ключа и добавлению секретов см. ниже в разделе "Подготовка VPS сервера" (шаги 2 и 5).
-
Установите необходимые зависимости:
# Обновление системы sudo apt update && sudo apt upgrade -y # Установка Docker и Docker Compose curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo apt install docker-compose-plugin -y # Установка Git sudo apt install git -y
-
Настройте SSH ключ для GitHub Actions:
Шаг 1: Создайте SSH ключ (на вашей локальной машине)
# Сгенерируйте новый SSH ключ специально для GitHub Actions ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions_deploy # Или используйте существующий ключ (если у вас уже есть SSH ключ для VPS) # В этом случае пропустите этот шаг
Шаг 2: Скопируйте публичный ключ на VPS сервер
# Если создали новый ключ ssh-copy-id -i ~/.ssh/github_actions_deploy.pub user@your-vps-ip # Или если используете существующий ключ ssh-copy-id user@your-vps-ip # Проверьте подключение ssh -i ~/.ssh/github_actions_deploy user@your-vps-ip # или ssh user@your-vps-ip
Шаг 3: Скопируйте приватный ключ для GitHub Secrets
# Если создали новый ключ cat ~/.ssh/github_actions_deploy # Или если используете существующий ключ (обычно ~/.ssh/id_ed25519 или ~/.ssh/id_rsa) cat ~/.ssh/id_ed25519 # или cat ~/.ssh/id_rsa
Важно: Скопируйте весь вывод команды (включая строки
-----BEGIN OPENSSH PRIVATE KEY-----и-----END OPENSSH PRIVATE KEY-----) -
Клонируйте репозиторий на сервер:
cd /opt sudo git clone https://github.com/your-username/organization_catalog_ddd.git sudo chown -R $USER:$USER organization_catalog_ddd cd organization_catalog_ddd
-
Создайте файл
.envна сервере:# Скопируйте пример .env и отредактируйте под продакшн cp .env.example .env nano .envОбновите переменные для продакшн окружения:
API_PORT=8000 POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_DB=organization_catalog POSTGRES_USER=postgres POSTGRES_PASSWORD=your_secure_password # ... остальные переменные
-
Добавьте SSH ключ в GitHub Secrets:
Шаг 1: Откройте настройки репозитория в GitHub
- Перейдите в ваш репозиторий на GitHub
- Нажмите на Settings (в верхней панели)
- В левом меню выберите Secrets and variables → Actions
Шаг 2: Добавьте секреты
Нажмите New repository secret и добавьте каждый секрет:
-
Name:
VPS_SSH_PRIVATE_KEYValue: Вставьте весь приватный ключ (который вы скопировали на шаге 2.3)- Должен начинаться с
-----BEGIN OPENSSH PRIVATE KEY----- - И заканчиваться на
-----END OPENSSH PRIVATE KEY----- - Включая все строки между ними
- Должен начинаться с
-
Name:
VPS_HOSTValue: IP адрес или домен вашего VPS (например,123.45.67.89илиexample.com) -
Name:
VPS_USERValue: Имя пользователя для SSH (обычноroot,ubuntu, илиdebian) -
Name:
VPS_APP_DIRValue: Путь к директории проекта на сервере (например,/opt/organization_catalog_ddd) -
Name:
VPS_APP_URL(опционально) Value: URL вашего приложения для health check (например,http://your-domain.comилиhttps://api.example.com)
Важно: После добавления секретов они будут зашифрованы и их нельзя будет просмотреть. Убедитесь, что сохранили значения где-то безопасно.
Проект использует скрипт deploy.sh для автоматизации деплоя. Скрипт выполняет:
- Обновление кода из репозитория
- Проверку наличия
.envфайла - Остановку старых контейнеров
- Сборку и запуск новых контейнеров
- Ожидание готовности PostgreSQL
- Создание базы данных (если не существует)
- Применение миграций Alembic
- Очистку старых Docker образов
- Проверку статуса контейнеров
Ручной запуск деплоя на VPS:
cd /opt/organization_catalog_ddd
chmod +x deploy.sh
./deploy.sh master # или другая веткаПосле настройки, деплой будет автоматически запускаться при:
- Push в ветку
masterилиmain - Ручном запуске через GitHub Actions (Actions → Deploy to VPS → Run workflow)
Важно: Перед деплоем автоматически выполняются проверки:
- Линтинг кода — проверка стиля кода с помощью
ruffиisort - Тесты — запуск всех тестов через
pytest
Деплой на VPS произойдет только если все проверки пройдут успешно. Это гарантирует, что на продакшн попадает только проверенный код.
После успешного деплоя проверьте:
-
Статус контейнеров:
ssh user@your-vps-ip "cd /opt/organization_catalog_ddd && docker ps" -
Health check endpoint:
curl http://your-vps-ip:8000/healthcheck
-
Логи приложения:
ssh user@your-vps-ip "cd /opt/organization_catalog_ddd && docker logs main-app"