Структура проекта и подготовка к продакшену

Промышленный FastAPI-проект разбит на модули по доменам, настраивается через переменные окружения и запускается в продакшене несколькими воркерами под управлением процесс-менеджера, обычно в Docker-контейнере.

Зрелый проект отличают три вещи: понятная модульная структура, конфигурация из окружения (а не из кода) и продакшен-запуск с несколькими воркерами вместо одиночного --reload.

Учебный пример живёт в одном файле, но реальное приложение требует структуры. Типичная раскладка делит код по слоям и доменам: роутеры (эндпоинты), модели (Pydantic-схемы), сервисы (бизнес-логика), работа с БД, конфигурация. Это упрощает навигацию, тестирование и совместную работу.

app/
  main.py            # создание FastAPI, lifespan, подключение роутеров
  config.py          # настройки из переменных окружения
  routers/
    users.py         # APIRouter для пользователей
    items.py
  models/
    user.py          # Pydantic-схемы
  services/
    user_service.py  # бизнес-логика
  db.py              # engine, сессия-зависимость

Конфигурацию берут из окружения, а не зашивают в код: секреты, строки подключения, флаги. Pydantic предлагает для этого BaseSettings, который читает переменные окружения и валидирует их как обычную модель. Так один и тот же образ работает в разных средах, меняя поведение через переменные.

Как работает под капотом

Чтение настроек из окружения с дефолтами и проверкой типов — это та же валидация, что у Pydantic-модели. Смоделируем загрузку конфигурации на stdlib:

import os

# имитируем переменные окружения
fake_env = {"APP_PORT": "8080", "DEBUG": "false", "DB_URL": "postgresql://prod"}

def load_settings(env):
    def get(key, default, cast=str):
        raw = env.get(key, default)
        if cast is bool:
            return str(raw).lower() in ("1", "true", "yes")
        return cast(raw)
    return {
        "port":  get("APP_PORT", "8000", int),
        "debug": get("DEBUG", "true", bool),
        "db_url": get("DB_URL", "sqlite:///./local.db"),
    }

settings = load_settings(fake_env)
print("настройки из окружения:", settings)
# тот же код, но окружение пустое -> сработают дефолты
print("настройки по умолчанию:", load_settings({}))

Попробуй сам ▶ Один код даёт разные настройки в зависимости от окружения — это и есть «12-факторный» принцип конфигурации, который реализует BaseSettings.

В продакшене приложение запускают не через --reload, а несколькими процессами-воркерами, чтобы использовать все ядра. Команды:

fastapi run app/main.py --workers 4
gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 4

Чаще всего это упаковывают в Docker, чтобы окружение было воспроизводимым:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["fastapi", "run", "app/main.py", "--workers", "4"]

Частые ошибки

Первая — тащить весь код в один файл по мере роста проекта. Вторая — хранить секреты в коде вместо переменных окружения. Третья — запускать прод с --reload и одним воркером, теряя производительность и устойчивость. Четвёртая — не фиксировать версии зависимостей, получая «работает у меня». Пятая — открывать наружу --reload//docs и отладочные настройки в продакшене.

Best practices

  • Делите проект по доменам и слоям; держите main.py тонким.
  • Конфигурацию читайте из окружения через BaseSettings; секреты — только в окружении.
  • В проде — несколько воркеров под процесс-менеджером, без --reload.
  • Фиксируйте версии зависимостей и упаковывайте в Docker для воспроизводимости.

Наблюдаемость в продакшене

Развернуть сервис — половина дела; его нужно ещё видеть в работе. Зрелый продакшен включает три кита наблюдаемости. Логи — структурированные (в JSON), с корреляционным идентификатором запроса, чтобы по одному id проследить весь путь обращения через сервисы. Метрики — счётчики запросов, латентность по перцентилям, доля ошибок, использование пула соединений; их собирают и строят дашборды, чтобы замечать деградацию до жалоб пользователей. Трассировка — сквозное прослеживание запроса через несколько сервисов, незаменимое в микросервисной архитектуре. Плюс health-check эндпоинт, по которому оркестратор понимает, жив ли сервис. Без наблюдаемости продакшен превращается в чёрный ящик, где о проблемах узнают последними. Поэтому подготовка к проду — это не только воркеры и Docker, но и встроенные с самого начала логи, метрики и проверки здоровья, превращающие сервис из «работает на моей машине» в управляемую и диагностируемую систему.

Итог: зрелый проект — это модульная структура, конфигурация из окружения и продакшен-запуск несколькими воркерами, обычно в Docker. Эти практики превращают учебный main.py в надёжный сервис.

Проверьте себя
1. Откуда промышленное FastAPI-приложение должно брать секреты и строки подключения?
AЖёстко прописанными в коде
BИз переменных окружения (например, через Pydantic BaseSettings)
CИз тела каждого запроса
DИз комментариев в коде
2. Как правильно запускать FastAPI в продакшене?
Auvicorn main:app --reload с одним процессом
BНесколькими воркерами под процесс-менеджером (например, fastapi run --workers 4 или gunicorn с UvicornWorker), без --reload
CПрямо из интерактивного интерпретатора Python
DТолько через TestClient