На связи снова Архитектурный комитет компании SimbirSoft, и мы продолжаем наш цикл статей, посвященных Design API First. Ранее мы уже писали о том, что представляет собой этот подход, приводили пример спецификации для сервиса аутентификации и рассказывали, как мы интегрируем этот паттерн в наш конвейер разработки.
Сегодня мы немного отвлечемся от бэкенда и разберем автоматизацию одной из рутинных задач на стороне frontend-разработки, а именно — описание моделей интерфейсов для взаимодействия фронта с беком, также написание API-сервисов, в которых фиксируются endpoints, методы запросов и формат передачи данных (query-параметры, заголовки, тело).
Инструменты кодогенерации
В нашей компании активно применяется стандарт OpenAPI. Несмотря на свою объемность в сравнении с более молодыми форматами, он является проверенным и наиболее отлаженным. Для него существует множество инструментов для поддержания документации. Поэтому рассмотрим несколько популярных инструментов, позволяющих работать с этим стандартом:
swagger-typescript-api — генерирует модели и сервисы (Fetch или Axios), есть возможность написания своих шаблонов кодогенерации.
openapi-typescript очень легковесный инструмент, заточенный под фронтендера, с хорошей поддержкой и без лишних зависимостей. Раскладывает спецификацию на набор интерфейсов, далее либо применяем самостоятельно данный инструмент, либо пользуемся вспомогательными — openapi-fetch или openapi-typescript-fetch, которые примут в качестве дженерика полученные ранее интерфейсы. Клиентов для axios или angular под openapi-typescript, к сожалению, не существует.
openapi-typescript-codegen – инструмент, аналогичный предыдущему, но дающий чуть больше кастомизации для шаблонов, в том числе тут есть шаблоны клиентов под axios или angular.
openapitools/openapi-generator-cli — это по сути Node.js-обертка над Swagger Codegen, который поддерживается создателями Swagger и стандарта OpenApi. Невероятно мощный инструмент: может генерировать клиентов не только для frontend, но и для backend. Есть возможность передачи кастомных шаблонов, можно настроить кодогенерацию под конкретный формат клиентов (Fetch, Angular, RxJS и другие).
Из npm-трендов (Рис. 1) видно, что openapi-typescript догоняет по популярности openapitools/openapi-generator-cli. Почему так происходит?
Во-первых, последний инструмент в зависимостях имеет Java — frontend-разработчику приходится ставить JRE или запускать генерацию в докере. Для запуска в докере есть специальный флаг в конфиге генератора, поэтому писать докер-файл не нужно, все запускается «под капотом».
Во-вторых, openapi-typescript предоставляет лишь корректную конвертацию в интерфейсы ТS, дальше с ними можно сделать что угодно. В то время как последний инструмент генерит еще и клиентов, и, если они не подойдут, приходится изучать громоздкую документацию и перенастраивать шаблоны кодогенерации.
Разберем пример на основе openapi-typescript-codegen и спеки сервиса авторизации из первой статьи.
Выполняем команды:
$ npm i openapi-typescript-codegen
$ npx openapi --input=spec.yml --output=api --client=fetch
И получаем следующую структуру:
.
└── api/
├── core/
├── models/
├── services/
└── index.ts
Директория core содержит базовые переиспользуемые модели, связанные непосредственно с кодогенертором. Папка models содержит все упомянутые в спеке модели, services содержит клиенты (URL для endpoints в связке с нужными моделями). Примеры моделей и сервиса приведены ниже.
Примеры моделей
export type Auth_Request_Model_AuthAccount = {
/**
* Email, привязанный к аккаунту пользователя
*/
userLogin: string;
};
export type Auth_Response_Model_AbstractSuccessAccessAccount = {
accessToken: string;
/**
* ID аккаунта пользователя
*/
accountId?: string;
message: string;
};
Пример сервиса
import type {Auth_Request_Model_AuthAccount} from "../models/Auth_Request_Model_AuthAccount";
import type {Auth_Response_Model_Account} from "../models/Auth_Response_Model_Account";
import type {Auth_Response_Model_WaitingAccessAccount} from "../models/Auth_Response_Model_WaitingAccessAccount";
import type {CancelablePromise} from "../core/CancelablePromise";
import {OpenAPI} from "../core/OpenAPI";
import {request as __request} from "../core/request";
export class AuthService {
/**
* Auth/R2. Метод получения доступа к аккаунту пользователя
* Метод предназначен для аутентификации пользователя под указанным email, соответствующим аккаунту в БД
* @param requestBody
* @param acceptLanguage https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Accept-Language
* @param correlationId
* @param platform
* @returns Auth_Response_Model_WaitingAccessAccount - Ожидается активация созданного аккаунта пользователя
* - Ожидается подтверждение входа пользователя в свой аккаунт
* - Ожидается подтверждение восстановления доступа пользователя к своему аккаунту
*
* @throws ApiError
*/
public static authAccount(requestBody: Auth_Request_Model_AuthAccount, acceptLanguage?: string, correlationId?: string, platform?: "MOBILE" | "WEB"): CancelablePromise<Auth_Response_Model_WaitingAccessAccount> {
return __request(OpenAPI, {
method: "POST",
url: "/v1/signin",
headers: {
"Accept-Language": acceptLanguage,
CorrelationID: correlationId,
Platform: platform,
},
body: requestBody,
mediaType: "application/json;charset=UTF-8",
errors: {
400: `Некорректные входные данные. Возвращается список атрибутов с ошибками`,
403: `Пользователю с текущими правами доступ отклонён`,
500: `Внутренняя ошибка сервера`,
},
});
}
/**
* Auth/R3. Метод получения информации об аккаунте
* Метод предназначен для получения информации из БД об аккаунте текущего пользователя
* @param acceptLanguage https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Accept-Language
* @param correlationId
* @returns Auth_Response_Model_Account Сформирован ответ с информацией об аккаунте текущего пользователя
* @throws ApiError
*/
public static getAccount(acceptLanguage?: string, correlationId?: string): CancelablePromise<Auth_Response_Model_Account> {
return __request(OpenAPI, {
method: "GET",
url: "/v1/auth",
headers: {
"Accept-Language": acceptLanguage,
CorrelationID: correlationId,
},
errors: {
401: `Пользователь не был аутентифицирован`,
500: `Внутренняя ошибка сервера`,
},
});
}
}
Рассмотрим несколько вариантов реализации инфраструктуры.
Разработка в монорепозитории
Поскольку для кодогенерации нужна спецификация, которую никто не хранит в директории с кодом frontend, то одним из естественных решений для такой связности является использование монорепозитория. Это в каком-то плане классический подход (Рис. 2).
Backend и frontend разрабатываются вместе, что дает удобный доступ к спецификации. При этом не важно, что первично: спецификация или код — мы либо сразу генерируем модели, либо запускаем ещё промежуточную команду по экстракции спеки из кода.
Разработка в раздельных репозиториях
Кодогенерация моделей при хранении кода frontend и backend в раздельных репозиториях сильно зависит от выбранного воркфлоу на проекте.
1. С тестированием в общей ветке
Тестирование в общей ветке применяется достаточно часто. Тут сказывается ограниченность ресурсов располагаемых серверов, не всегда есть возможность раскатать множество инстансов бекенда с непустой базой для разработки и тестирования.
1.1. Для случаев, когда тестирование делается в общей ветке, можно дождаться, когда сборку backend накатят на стенд Dev, отчего спецификация перезальется, и frontend-специалист сможет ее спокойно скачать по известному URL (Рис. 3).
1.2. Если на проекте проектированию отдельных кусков функциональности уделяется должное внимание, происходит согласование форматов взаимодействия frontend и backend. И/или есть сильная потребность запускать разработку параллельно (вести разработку frontend вместе с backend, а не после оного), то вам может потребовать отдельный репозиторий для хранения спецификаций. Отличия от предыдущего варианта минимальны (Рис. 4) — тут backend генерирует и для себя клиентов.
1.3. Если спецификация лежит статично и не нуждается в какой-либо компиляции, то можно «сходить» в соседний репозиторий и забрать ее оттуда, не дожидаясь деплоя на стенд (Рис. 5). Для авторизации curl-a в gitlab или bitbacket используем Access Token, сгенерированный под конкретного человека, в Git не храним (добавляем в gitignore).
2. С тестированием отдельных веток на отдельных стендах
Если перед вливанием в общую ветку ведется отдельное тестирование на отдельном стенде, то все становится интересней и сложней. Подходы 1.1-1.3 можно переиспользовать (и в 90% случаев их хватает), но уже в отношении отдельных стендов. В зависимости от задачи надо только не забывать менять URL на спецификацию.
Как вариант, для особо сложных случаев можно кодогенерацию вынести в CI, клиенты будут артефактами сборки. В дальнейшем клиенты публикуются в корпоративном npm. Для версионирования используются «версия продукта» + «имя ветки» (например, @project-scope/api@1.2.3-task_123). Если пакета с таким номером задачи не существует, то ставим по дефолту последний пакет из общей ветки (например, @project-scope/api@1.2.3-dev). Отметим, что ставить пакеты нужно с флагом «no-save», так как от задачи к задаче будут появляться новые версии пакетов (Рис. 6).
Заключение
Итак, в этой статье мы разобрали несколько инструментов кодогенерации, которые могут полезны frontend-разработчику, описали типовые схемы реализации инфраструктуры. Конечно, выбор определенной схемы зависит от конкретных задач, но вывод все равно очевиден: если вы используете на проекте подход Design API First, то пользуйтесь кодогенераторами — это позволит автоматизировать небольшую работу в пределах одной задачи, но огромную в рамках всего проекта. Также поможет избавиться от ошибок из-за человеческого фактора при интеграции с backend.
При наличии удобного доступа к спецификации пользуйтесь подходами 1.1-1.3 — просто забирайте ее из соседней директории или curl-ом.
В противном случае можно присмотреться к npm как к очень удобной системе доставке кода. Например, на проектах с микросервисами и микрофронтами.
Пользуетесь ли вы подобной кодогенерацией? Как доставляете спецификацию или готовый код на фронт? Какие интересные кейсы вам попадались? Пишите в комментариях, будем рады обсудить это вместе.
Другие полезные материалы для разработчиков и архитекторов в IT публикуем в наших соцсетях — ВК и Telegram.
onegreyonewhite
А мы вот на лету научились парсить схему и делать приложения практически без написания js/ts кода. Это я так, хвастаюсь. А если серьёзно, то это капец удобно. Очень много времени на разработку экономит. Но требует определённой дисциплины: стандартные интерфейсы, изначально качественный дизайн и т.п.
Используем swagger-typescript-api, но там мерзкий баг с camelCase преобразованием из snake_case. Спасибо, что подкинули куда ещё посмотреть.