[dev] Backend разработка - profcomff/.github GitHub Wiki
Note Документации по всем API можно посмотреть тут: https://api.profcomff.com/
В нашей команде разработка ведется с использованием python 3.11. Используемые библиотеки: Pydantic, FastAPI, asyncio, SQLAlchemy, alembic, docker, black, pytest.
Все зависимости прописываются в файле requirements.txt без указания версий(для того, чтобы они автоматически скачивали самую новую). Разработческие зависимости можно прописывать в requirements.dev.txt. Там мы точно указываем black(https://pypi.org/project/black/). Если вы склонировали один из наших проектов, то посыле настройки конфигурации запуска и создания venv, стоит запустить pip install -m requirements.txt. После этого все зависимости установятся.
Для конфигурации мы используем переменные окружения. Чтобы многократно не заполнять них, мы используем файлы окружения. Для работы с файлами окружения мы используем Pydantic-settings
. В файле settings.py создается класс Settings extends BaseSettings, в котором определятся, что должно быть в .env файле. Здесь и везде далее используются type hints. Сам python не строго относится к их соблюдения, только подсказывает вам в IDE, что вы должны передать в качестве аргументов куда-либо, где указаны типы. Однако Pydantic проверяет соответствие указанных типов и полученных данных, что очень удобно и безопасно.
https://fastapi.tiangolo.com/advanced/settings/#pydantic-settings
Локально создается .env
файл в корне проекта. То есть, он будет виден в рабочем каталоге как .env
, а не как ../.env
и прочие вариации путей до него. То есть в файле settings.py
прописывается что то такое:
from pydantic import PostgresDsn
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings"""
DB_DSN: PostgresDsn = 'postgresql://postgres@localhost:5432/postgres'
model_config = SettingsConfigDict(case_sensitive=True, env_file=".env", extra='ignore')
Пример .env файла, например, из бэкенда расписания: https://github.com/profcomff/timetable-backend/blob/main/.env.example.
Когда вы первый раз отправляете коммит, проверяйте наличие файла .gitignore
, .env
файл не должен попасть в изменения.
Для работы с БД мы используем SQLAlchemy. Это очень мощный инструмент разработки с встроенным ORM, который мы в основном и используем. Смысл ORM - установка соответствий между моделями с БД и объектами в python. Таким образом, вы работаете с экземплярами соответствующих классов, а не с чистыми SQL-запросами, которые зачастую очень громоздкие и неудобные в дальнейшей работе с ними.
Для начала, создаются все модели(классы) в python. Путь к ним обычно выглядит как /models/db.py. Поля таких классов - колонки. При создании таблиц sqlalchemy.Column
создает нужную колонку, а в дальнейшем при обращении к этому полю, выдает значение для текущей строки в БД.
https://docs.sqlalchemy.org/en/20/orm/quickstart.html#declare-models
После создания моделей, описываются relationships между ними. Это нужно для простой и быстрой реализации больших и, кажется, ненужных запросов. То есть, childrens = session.query(Parent).filter(Children.parent_id == parent.id).all()
превращается в parent.childrens
. Очень упрощает читаемость кода.
https://docs.sqlalchemy.org/en/20/orm/basic_relationships.html
Отношения между моделями бывают разные: one-to-one, many-to-one, many-to-many. Все их реализации достаточно просты, но в любом случае требуют изучения:
- https://docs.sqlalchemy.org/en/20/orm/basic_relationships.html#one-to-many
- https://docs.sqlalchemy.org/en/20/orm/basic_relationships.html#one-to-one
- https://docs.sqlalchemy.org/en/20/orm/basic_relationships.html#many-to-many
В процессе разработки структура БД может меняться, для того, чтобы это все автоматически раскатывалась на сервере, мы используем alembic. Любые изменения в БД должны сопровождаться генерацией файла миграций. Ваша БД должна содержать только те таблицы, которые нужны в данном проекте.
Для создания миграций надо сделать следующие действия:
alembic revision --autogenerate -m "issue_number_and_name"
- Проверьте, что в папке
migrations/versions
создался новый файлик и его содержимое соответствует вашим ожиданиям- Если что-то сформировалось не так, обязательно поправьте. Обычно переименования колонок или таблиц алембиком воспринимаются как удаление и создание новых, это ошибка!
- Можно натравливать автоформатирование, чтоб делать файлики миграций красивыми :)
-
alembic upgrade head
для применения миграций
Для работы с вебом мы используем FastAPI. В нем есть все необходимое и весит он меньше, чем Django :)
Работаем мы по стандартам REST API: https://habr.com/ru/post/483202/
Для конкретного объекта создается свой собственный роутер, в котором минимум указывается prefix
и tags
. prefix
- это то, что будет всегда стоять в начале пути для данного роутера, tags
- для удобства чтения документации в Swagger UI. Все ручки являются асинхронными.
REST API состоит из нескольких типов ручек: GET/POST/PATCH/DELETE.
-
GET запрос не может иметь тело в виде json. Его не стоит использовать для передачи конфиденциальных данных, т.к браузер может сохранять историю запросов и т.д. Запрос будет выглядеть так GET /router/{id} для конкретного ресурса и GET /router для всех
-
POST запрос уже может содержать тело в виде json. Его используют для создания ресурса. JSON передается в виде верифицируемой модели pydantic. То есть, описывается класс с полями, которые должен содержать передаваемый json. Опять же указываются type hints и необходимость передачи того или иного поля (например
id: int | None
). Запрос будет выглядеть так: POST /router -
PATCH запрос во многом похож на POST(не рассматриваемый здесь PUT туда же). Используется для редактирования ресурса. Во многом это просто создано для удобства, по факту можно использовать для обновления и POST запросы. Но это противоречит спецификации REST, так что мы так не делаем. Запрос будет выглядеть так: PATCH /router/{id}
-
DELETE аналогичен, используется для удаления ресурсов. Вместо него так же можно использовать другие запросы с постфиксами /delete, но так как мы работаем по REST API, мы используем этот тип запросов. Пример запроса: DELETE /router/{id}
Каждый роутер складывается в отдельный файл, потом они собираются вместе в файле base.py
. Все роутеры лежат в директории /routes/ вместе с base.py.
Когда ручка что то возвращает, мы используем json словари, верифицируемые Pydantic'ом. Аналогично передаче данных в запрос, описанной выше: описывается класс с полями, которые должен содержать получаемый json. Опять же указываются type hints и необходимость содержания того или иного поля(например id: int | None
) в ответе. Если запрос может возвращать большое количество данных, используется пагинация: ответ от сервера в таком случае представляет из себя: {"items": result, "limit": limit, "offset": offset, "total": result.count()}
, где limit
- максимальный размер ответа, offset - смещение от первого элемента, total - количество подходящих записей в БД.
В base.py
подключаются middlewares. Про CORS, используемый, когда фронт лежит отдельно от бэка можно почитать тут: https://github.com/profcomff/.github/wiki/Расположение-сервисов
Подключаемый DBSessionMiddleware
нужен для создания сессии работы с БД прямо в FastAPI.
Также, можно подключать и собственные middlewares.
Для начала, вы должны помнить следующее:
-
Все пароли, Redierct URLs и прочее указываются в
.env
файле и подгружается в классSettings
. Мы не храним пароли и адреса наших серверов в коде. -
Кроме requirements.txt в корне проекта лежат файлы:
.env
,.gitignore
,flake8.conf
,pyproject.toml
,LICENSE
. Это все файлы конфигураций и файлы для GitHub - вообще относительно разработческие штуки. Их можно взять в любом готовом проекте (кроме.env
) -
Если вы создаете PR в пустом репозитории: В корне проекта создается исполняемый модуль и модуль тестов, папка с миграциями, путь
/.github/workflows/
. В каждой директории модулей должен лежать файл__init__.py
, а в директории исполняемого модуля должен лежать и файл__main__.py
. Запускать надо исполняемый модуль(python3 -m...) -
Любые изменения в БД должны сопровождаться генерацией файла миграций. Ваша БД должна содержать только те таблицы, которые нужны в данном проекте.
-
Любые изменения в структуре проекта должны быть прописанными в
Dockerfile
. При создании папок, если они пустые, создавайте там пустой файл.gitkeep
, иначе папка может не закоммититься. После этого не забудьте прописать этот путь вDockerfile
.
- Создать ветку в GitHub, работать в ней
- Написать код, решающий задачу
- Написать тесты к вашему коду
- Проверить, сгенерирован ли файл миграций
- Проверить, все ли папки, созданные вами, закоммитились в вашу ветку. Проверить, добавили ли вы их в Dockerfile
- Проверить соответствие пропета стандартам PEP8, прогнать по коду black
- Запросить review у любого из старших разработчиков
- Исправить ошибки, если присутствуют на этапе review
- Merge! Вы великолепны.
Если вы создаете новый проект и хотите упростить себе жизнь и не заниматься структурой проекта, можете использовать наш готовый шаблон проекта: https://github.com/profcomff/fastapi-template
Чтобы новый сервис появился в тесте и проде нужно сделать несколько важных вещей:
-
Подготовить БД (это надо повторить и в тестовой БД, и в продовой)
- Создать новых пользователей в тестовой и продовой базах данных:
CREATE USER srvc_test_marketing_api IN GROUP group_service_test PASSWORD '...';
- Создать новые схемы для хранения таблиц:
create schema api_marketing authorization srvc_test_marketing_api;
- Назначить для пользователя схему по умолчанию:
alter user srvc_test_marketing_api set search_path to api_marketing;
- Создать новых пользователей в тестовой и продовой базах данных:
-
Настройка репозитория
- Создать среды исполнения (Environments) для теста и прода (обычно во всех репозиториях Testing и Production)
- В средах создать переменные с ключами, необходимые для запуска сервиса. Например данные для подключения к БД мы кладем в переменную
DB_DSN
:
На изображении 2 ключа, к которым можно обратиться из CI через{{ secrets.НАЗВАНИЕ }}
- На прод настроить ревьюера, который сможет выкатывать сервис для всех пользователей.
-
Упаковать проект для запуска в Docker
- Создать
Dockerfile
в корне проекта. Пример докерфайла
- Создать
-
Настроить GitHub Actions для автоматического запуска сборки и запуска на сервере
- Создать папку
.github/workflows
- Положить в него файлики для раскатки теста, прода, тестирования и т.д.
- В качестве шаблона можно использовать этот пример. Тут происходит раскатка в тест при коммитах в ветку
main
и раскатка в прод при создании тегов.
- Создать папку
-
Настроить сервер
- Сервисы находятся в отдельных докер контейнерах, их видно только во внутренней сети
- У нас есть Caddy (это reverse proxy http сервер), который запросы из внешней сети прокидывает во внутреннюю. В него надо добавить новую запись reverse_proxy.
Пример Caddy записи
printer.api.profcomff.com:443 { reverse_proxy com_profcomff_api_printer:80 }
- Понять, что по ссылкам в прошлом пункте ничего не понятно и просто скопировать готовую конфигурацию соседнего сервиса :)
- Просим админов БД скинуть нужный дамп (получить его можно только если есть соответствующие доступы)
Команда для дампа:
pg_dump -U {role} -h {host} -p {port} -d {db_name} -n {schema_name} > /tmp/{name}.dump
- Кидаем его в БД:
psql -U {role} -h localhost -p 5432 -d {db_name} < /tmp/{name}.dump
- Применяем миграции на локальной БД.