Введение
Когда я встал на путь становления fullstack’ом, передо мной появилась проблема: Для того, чтобы спроектировать простенький api для тестирования своих frontend-приключений, необходимо создать fastapi-приложение, развернуть базу данных, настроить подключение к бд с помощью алхимии и так далее.
После нескольких почти одинаковых разворачиваний «простенькой» апишки, которые забирали достаточно много времени, я решил, что пора уже наконец создать репозиторий-шаблон для быстрого разворачивания FastAPI с минимально-необходимой инфраструктурой, чтобы он работал из коробки.
В этой статье я хочу привести свое видение того, как должна выглядеть базовая инфраструктура FastAPI, и описать, как, что и почему я решил включить в структуру своего fastapi-шаблона. Если вы знаете варианты, как сделать лучше — пишите в комментариях, я обязательно это попробую. Я планирую улучшать свой шаблон по мере того, как буду набираться знаний и опыта.
Ссылка не репозиторий с шаблоном: https://github.com/max31ru12/FastAPI-Template
Немного про мой опыт
Я работаю разработчиком в крупной IT-компании, занимающейся, в основном, работой с данными или же базами данных. На работе я использую Django и React, но я также имею опыт разработки на FastAPI в стартапе.
База данных
Я еще не оказывался в такой ситуации, чтобы с каким-то python-приложением мне было необходимо развернуть нечно иное, чем postgres. Поэтому в качестве субд была выбрана постгря, развернутая в докере.
Конфигурация файла docker-compose для БД:
services: postgres: container_name: postgres image: postgres:alpine environment: - POSTGRES_USER=test - POSTGRES_DB=test - POSTGRES_PASSWORD=test volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U test"] interval: 30s timeout: 10s retries: 5 networks: - default
Тут все довольно просто:
-
использую
postgres:alpine
для того, чтобы контейнер занимал меньше места; -
прокидываю базовые переменные окружения для postgres’а;
-
волюм обязательно;
-
healthcheck
по желанию.
В принципе, БД можно использовать любую, но самый частный кейс в моей практике — postgres. Если понадобится, например, Mongo, то придется написать новый сервис.
Структура бэкенда
Шаблон не должен навязывать слепое следование какой-то определенной структуре папок. Поэтому основная директория, где расположен бэкенд, не имеет строгой структуры файлов и папок. Основное приложение находится в директории app/, где расположены папки:
-
models — модели SQLAlchemy;
-
api — все, что связано у роутингом;
-
utils — какие-то вспомогательные функции;
-
entities — pydantic модели.
Все эти папки (за исключение, наверное, api/) опциональны. Можно их выносить, удалять, объединять и т.п. Важно отметить, что в больших монолитах внутри основной директории /app могут быть различные приложения, внутри которых и будут находиться вышеперечисленные папки.
Внутри main.py
все стандартно. Из интересного могу отметить использование ORJSONResponse в создании инстанса приложения. Это позволяет ускорить работу с JSON, так как orjson — самая быстрая либа для работы с JSON (по крайней мере они так заявляют в своем репозитории).
В config.py
я решил вынести все, что связано с каким-то константами и переменными окружения. setup_db.py
содержит в себе все, что необходимо для конфигурации SQLAlchemy.
Миграции Alembic
Папку с миграциями alembic/ я решил расположить в корневой папке репозитория. Папка с миграциями у меня одна, поэтому ее нет особого смысла помещать в директорию с бэкендом. При появлении приложений внутри /app можно для каждого их них сделать отдельную папку с миграциями, как это реализовано в Django.
Инициализация таблиц — это полностью ответственность alembic, поэтому никакого ручного создания таблиц через:
async with test_engine.begin() as conn: await conn.run_sync(Base.metadata.create_all)
Тестирование
Все базовые настройки для тестирования эндпоинтов и взаимодействия с БД находятся в tests/contest.py
, так также фикстура для сессий. Тестовая БД создается для всех прогонов тестов и удаляется в конце. В test_example.py
два простых теста для того, чтобы пайплайн не прошел, если что-то связанное с БД сломалось.
CI/CD
Я написал простенький и не очень красивый пайплайн, который проверяет установку все зависимостей, прогоняет линтеры и тесты, а также поднимает docker-compose с БД и бэкендом.
Также есть pre-commit с несколькими линтерами.
Образ FastAPI
Для бэкенда я написал докер-файл, который находится по пути compose/backend/Dockerfile. В принципе, можно было оставить этот файл в корневой папке проекта, но что если надо будет написать еще один Dockerfile для какого-нибудь celery-воркера? Именно для таких ситуаций я разделил докер-файлы по папкам внутри директории compose.
Конфигурация докер-файла:
FROM python:3.12-slim RUN apt-get update -qy ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 WORKDIR /backend COPY requirements-dev.txt ./ RUN pip install -r requirements-dev.txt COPY ./app ./app COPY ./alembic ./ COPY alembic.ini ./ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload"] EXPOSE 8000
В образе все стандартно:
-
Образ python использую slim ради экономии места;
-
Использую pip. Иногда я использую poetry для fastapi-проектов, но пока что я не увидел на практике явных преимуществ poetry по сравнению с pip. Понятное дело, что они есть, но они не всегда нужны;
-
alembic — папка с миграциями. Можно перенести ее в директорию приложения app;
-
alembic.ini меняться вряд ли будет, поэтому скопируем его отдельной командой, чтобы docker его закэшировал.
Сервис для бэкенда в docker-compose файле:
backend: container_name: backend build: context: . dockerfile: compose/backend/Dockerfile depends_on: - postgres environment: - DB_HOST=${DB_HOST:-postgres} - DB_PORT=${DB_PORT:-5432} - DB_PASSWORD=${DB_PASSWORD:-test} - DB_USER=${DB_USER:-test} - DB_NAME=${DB_NAME:-test} - DEV_MODE=${DEV_MODE:-true} ports: - "8000:8000" restart: unless-stopped volumes: - ./app/:/backend/app/ - ./alembic/:/backend/alembic networks: - default
Запуск приложения
Любое приложение, построенное на основе моего шаблона, запускается с помощью команды:
docker compose -f local.yml up --build -d
После запуска контейнеров бэкенд будет доступен по стандартному адресу.
Миграции проводятся через контейнер с бэкендом:
docker compose -f ./local.yml run --rm backend alembic upgrade head
Аналогично можно создавать миграции:
docker compose -f ./local.yml run --rm backend alembic revision --autogenerate -m "name" --rev-id "001"
Заключение
Шаблон репозитория получился довольно топорный, но имеющий базовую инфраструктуру для разработки и тестирования, а также возможности для более гибкой настройки и пути для масштабирования приложения.
Из явных проблем могу отметить небольшие танцы с бубном при использовании удаленного интерпретатора из докера, но это терпимо. Также можно написать более красивый пайплайн и, при необходимости, более продуманно разместить миграции и конфигурационные файлы, но это вкусовщина и зависит от масштаба бэкенда.
ссылка на оригинал статьи https://habr.com/ru/articles/871018/
Добавить комментарий