Удобство разработки - одна из самых важных вещей для программиста, особенно indie-разработчика, так как это напрямую влияет на мотивацию писать что-то новое. Если разрабатывать сложно и неудобно - мотивации что-то писать становится меньше.
Два пути fullstack-разработки
При разработке полноценного сервиса зачастую требуется создать и backend, и frontend. У меня, как у Python-программиста два пути:
- Писать и Backend, и Frontend на Python. В таком случае Frontend создается при помощи шаблонов (например Jinja2).
- Писать Backend на Python, а Frontend на одном из JavaScript-фреймворков.
Сегодня я бы хотел затронуть второй вариант: отдельная разработка Backend на Python и Frontend на JavaScript.
Проблема на стыке Backend и Frontend
У нас уже есть все необходимые инструменты, чтобы удобно писать Backend на Python и Frontend на TypeScript - авто-дополнение кода работает прекрасно, типы переменных проверяются и т.д.
На стыке же этих систем не все так хорошо. В ходе активной разработки сервиса, backend может часто меняться, в том числе могут ломаться контракты API.
В соответствии с этим, я бы хотел, чтобы:
- Обращение к методам API со стороны Frontend были максимально похожи на простой вызов функции.
- Должна быть возможность проверить линтером, что Frontend вызывает корректные методы API с корректными составом и типом параметров.
- При обновлении контрактов API со стороны Backend-сервиса, разработчик НЕ должен руками проходиться по всему Frontend-проекту и искать где и что сломалось, это должно происходить автоматически.
К счастью, решение есть - мы можем генерировать клиент к нашему Backend-сервису по OpenAPI спецификации.
В идеале, наш Backend также должен уметь сам генерировать OpenAPI спецификацию на основе сигнатур методов API. Этому условию удовлетворяют, например, Python-фреймворки FastAPI
и Litestar
Кодогенерация по OpenAPI на примере Litestar
Создадим максимально простой проект со следующей структурой:
.
├── backend
│ └── app.py
├── frontend
│ └── package.json
Рассмотрим пример с Litestar
. Для начала инициализируем виртуальное окружение и установим litestar
в качестве зависимости:
> cd ./backend
> python3 -m venv ./venv
> source ./venv/bin/activate
> pip install litestar
Далее напишем простейший сервер:
from litestar import Litestar, post
@post("/items/")
async def create_item(
name: str,
description: str,
) -> bool:
return True
app = Litestar(route_handlers=[create_item])
Сервер создает некоторый объект на основе его имени и описании. Как же нам получить OpenAPI спецификацию этого сервиса? Очень просто! Достаточно воспользоваться командой:
> litestar --app app:app schema openapi
Команда создаст файл openapi_schema.json
- это как раз и есть описание нашего сервиса в формате OpenAPI.
Теперь воспользуемся инструментом OpenAPI TypeScript
чтобы сгенерировать TypeScript типы для обращения к нашему серверу:
> npx openapi-typescript openapi_schema.json -o ../frontend/api_gen.d.ts
Для обращения к API теперь требуется создать клиент на основе сгенерированных типов. С этим нам поможет библиотека OpenAPI Fetch:
> cd ../frontend
> npm i openapi-fetch
Наконец, можем создать наш API-клиент:
import type { paths } from './api_gen';
import createClient from 'openapi-fetch';
export const apiClient = createClient<paths>();
Проверим, что клиент генерируется верно и корректно подсказывает нам контракт API:
Также, очень удобно, что клиент сам знает какие query-параметры принимает наш метод /items
:
Итог
Таким образом, мы можем максимально просто связывать наши frontend и backend сервисы, а также быть уверенными, что frontend вызывает методы API с правильными параметрами. Всего хорошего!