Как генерировать модели интерфейсов на основе спецификации на стороне frontend-приложений

от автора

На связи снова Архитектурный комитет компании SimbirSoft, и мы продолжаем наш цикл статей, посвященных Design API First. Ранее мы уже писали о том, что представляет собой этот подход, приводили пример спецификации для сервиса аутентификации и рассказывали, как мы интегрируем этот паттерн в наш конвейер разработки.

Сегодня мы немного отвлечемся от бэкенда и разберем автоматизацию одной из рутинных задач на стороне frontend-разработки, а именно — описание моделей интерфейсов для взаимодействия фронта с беком, также написание API-сервисов, в которых фиксируются endpoints, методы запросов и формат передачи данных (query-параметры, заголовки, тело).

Инструменты кодогенерации

В нашей компании активно применяется стандарт OpenAPI. Несмотря на свою объемность в сравнении с более молодыми форматами, он является проверенным и наиболее отлаженным. Для него существует множество инструментов для поддержания документации. Поэтому рассмотрим несколько популярных инструментов, позволяющих работать с этим стандартом:

  1. swagger-typescript-api — генерирует модели и сервисы (Fetch или Axios), есть возможность написания своих шаблонов кодогенерации.

  2. openapi-typescript очень легковесный инструмент, заточенный под фронтендера, с хорошей поддержкой и без лишних зависимостей. Раскладывает спецификацию на набор интерфейсов, далее либо применяем самостоятельно данный инструмент, либо пользуемся вспомогательными — openapi-fetch или openapi-typescript-fetch, которые примут в качестве дженерика полученные ранее интерфейсы. Клиентов для axios или angular под openapi-typescript, к сожалению, не существует.

  3. openapi-typescript-codegen – инструмент, аналогичный предыдущему, но дающий чуть больше кастомизации для шаблонов, в том числе тут есть шаблоны клиентов под axios или angular.

  4. openapitools/openapi-generator-cli — это по сути Node.js-обертка над Swagger Codegen, который поддерживается создателями Swagger и стандарта OpenApi. Невероятно мощный инструмент: может генерировать клиентов не только для frontend, но и для backend. Есть возможность передачи кастомных шаблонов, можно настроить кодогенерацию под конкретный формат клиентов (Fetch, Angular, RxJS и другие).

Рисунок 1. Популярные npm-пакеты для кодогенерации

Рисунок 1. Популярные npm-пакеты для кодогенерации

Из 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).

Рисунок 2. Реализация в монорепозитории

Рисунок 2. Реализация в монорепозитории

Backend и frontend разрабатываются вместе, что дает удобный доступ к спецификации. При этом не важно, что первично: спецификация или код —  мы либо сразу генерируем модели, либо запускаем ещё промежуточную команду по экстракции спеки из кода.

Разработка в раздельных репозиториях

Кодогенерация моделей при хранении кода frontend и backend в раздельных репозиториях сильно зависит от выбранного воркфлоу на проекте.

1. С тестированием в общей ветке

Тестирование в общей ветке применяется достаточно часто. Тут сказывается ограниченность ресурсов располагаемых серверов, не всегда есть возможность раскатать множество инстансов бекенда с непустой базой для разработки и тестирования.

1.1. Для случаев, когда тестирование делается в общей ветке, можно дождаться, когда сборку backend накатят на стенд Dev, отчего спецификация перезальется, и frontend-специалист сможет ее спокойно скачать по известному URL (Рис. 3).

Рисунок 3. Хранение спецификации в репозитории бекенда

Рисунок 3. Хранение спецификации в репозитории бекенда

1.2. Если на проекте проектированию отдельных кусков функциональности уделяется должное внимание, происходит согласование форматов взаимодействия frontend и backend. И/или есть сильная потребность запускать разработку параллельно (вести разработку frontend вместе с backend, а не после оного), то вам может потребовать отдельный репозиторий для хранения спецификаций. Отличия от предыдущего варианта минимальны (Рис. 4) — тут backend генерирует и для себя клиентов.

Рисунок 4. Хранение спецификации в отдельном репозитории

Рисунок 4. Хранение спецификации в отдельном репозитории

1.3. Если спецификация лежит статично и не нуждается в какой-либо компиляции, то можно «сходить» в соседний репозиторий и забрать ее оттуда, не дожидаясь деплоя на стенд (Рис. 5). Для авторизации curl-a в gitlab или bitbacket используем Access Token, сгенерированный под конкретного человека, в Git не храним (добавляем в gitignore).

Рисунок 5. Модифицированный способ хранения спецификации

Рисунок 5. Модифицированный способ хранения спецификации

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).

Рисунок 6. Вариант реализации с тестированием отдельных веток

Рисунок 6. Вариант реализации с тестированием отдельных веток

Заключение

Итак, в этой статье мы разобрали несколько инструментов кодогенерации, которые могут полезны frontend-разработчику, описали типовые схемы реализации инфраструктуры. Конечно, выбор определенной схемы зависит от конкретных задач, но вывод все равно очевиден: если вы используете на проекте подход Design API First, то пользуйтесь кодогенераторами — это позволит автоматизировать небольшую работу в пределах одной задачи, но огромную в рамках всего проекта. Также поможет избавиться от ошибок из-за человеческого фактора при интеграции с backend.

При наличии удобного доступа к спецификации пользуйтесь подходами 1.1-1.3 — просто забирайте ее из соседней директории или curl-ом.

В противном случае можно присмотреться к npm как к очень удобной системе доставке кода. Например, на проектах с микросервисами и микрофронтами.

Пользуетесь ли вы подобной кодогенерацией? Как доставляете спецификацию или готовый код на фронт? Какие интересные кейсы вам попадались? Пишите в комментариях, будем рады обсудить это вместе.

Другие полезные материалы для разработчиков и архитекторов в IT публикуем в наших соцсетях — ВК и Telegram


ссылка на оригинал статьи https://habr.com/ru/companies/simbirsoft/articles/751406/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *