Представьте ситуацию: ваша команда закончила интеграцию с API, все протестировала и подготовила релиз. А тут внезапно серверная команда меняет формат ответа. Фронтенд падает, QA злится, пользователи недовольны. Приходится вручную переписывать валидации, искать баги и терять дни, чтобы всё починить. 

Как автоматически синхронизировать работу между бэкэндом и фронтендом? А между дизайном и фронтендом? Использовать генератор кода валидации! Написание своего решения поможет понять бизнес-логику проекта и создать валидаторы, которые максимально отвечают вашим требованиям. А реализация проще, чем кажется на первый взгляд – покажу это в статье на примере создания кастомного генератора на нашем проекте. 

В первую очередь статья будет полезна фронтенд-разработчикам и командам, работающим над сложными веб-приложениями, особенно тем, кто активно взаимодействует с API, описанными через OpenAPI. 

Всем привет, меня зовут Ярослав и я веб-разработчик Outlines Tech. В ИТ я уже более 12 лет, 8 лет из которых занимаюсь фронтенд-разработкой на React.JS. Свой путь начинал с бэкенда: писал на PHP, Node.js и Python. Работал в разных отраслях: от ритейла до финтеха, сейчас развиваю банковские проекты. 

Зачем нам понадобилась кодогенерация или как мы устали переписывать валидации

Стандартом описания взаимодействия сервера и клиента по REST протоколу является OpenAPI спецификация. Но работать с JSON схемой не очень удобно – обычно мы используем библиотеки yup или zod, каждый раз вручную переписывая спецификации. Codegen redux toolkit query генерирует код только для отправки данных, но не для валидации. Поэтому, если бэкенд меняет паттерны валидации, то фронтенду не поступает информация и они продолжают валидировать данные по старым паттернам. 

Расскажу пример из практики:

На нашем проекте динамичные API. Каждое изменение в ответах сервера часто приводит к нарушению работы форм на фронтенде. Печально, когда команда QA узнает об этом в последний момент, иногда даже позже пользователей. 

Обновление OpenAPI спецификаций каждый раз приносит сложности. Компилятор TypeScript сразу начинает сигнализировать об ошибках в типах. Нам приходится разбираться в причинах и искать изменения в бэкенде, которые к этому привели. Разработчики сервера, конечно, стараются уведомлять нас о запланированных изменениях через релиз-ноутсы, но они могут затеряться. Один раз из-за изменения формата ответа сервера на запрос авторизации мы полностью переписывали все формы авторизации на проекте. 

Поддержание актуальности сообщений об ошибках – отдельная сложность. Регулярные выражения для валидации, например, требования к сложности пароля, часто меняются и сообщения об ошибках быстро устаревают. Нам приходится постоянно обновлять их, искать устаревшие элементы в кодовой базе и приводить их в соответствие с новыми требованиями. Работа в таких условиях превращается в бесконечную погоню за изменениями.

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

На что опирались: open-source решения на GitHub

При работе с OpenAPI каждая схема описывает структуру данных, например, запросов или ответов. Эта схема описывает обязательные поля и правила валидации, такие как длина строки и формат. Мы можем просто использовать эти данные, как проперти аргументы компонента формы. Приведу пример:

Данные из OpenAPi:

{
  "title": "User Registration",
  "type": "object",
  "required": ["username", "email", "password"],
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 30,
      "description": "User's unique username"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "User's email address"
    },
    "password": {
      "type": "string",
      "minLength": 8,
      "description": "Password for the user"
    }
  }
}

Код:

import Form from '@rjsf/core'; // react-jsonschema-form

// JSON Schema, сгенерированная из OpenAPI
const schema = {...};

// Дефолтные значения для формы
const uiSchema = {
  password: {
    "ui:widget": "password", // Визуальный вид для пароля
  },
};

// Обработчик отправки формы
const handleSubmit = ({ formData }) => {
  console.log("Submitted data:", formData);
};

const App = () => {
  return (
    <div>
      <h1>Registration Form</h1>
      <Form
        schema={schema} // JSON Schema
        uiSchema={uiSchema} // Визуальная настройка
        onSubmit={handleSubmit} // Обработчик отправки
      />
    </div>
  );
};

Какие есть преимущества у подхода? 

  • Полная синхронизация с бэкендом: JSON Schema обновляется автоматически из OpenAPI.

  • Минимум ручной работы: нет необходимости вручную прописывать правила валидации.

  • Удобный пользовательский интерфейс: легкая настройка внешнего вида через uiSchema.

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

Мы рассмотрели и другие решения с необходимым функционалом:

OpenAPI-Zod-Client
Генерирует не только схему валидации, но и полноценный клиент-сервисный слой для взаимодействия между клиентом и бэкендом. Его цель – заменить tRPC. Генерация валидаторов в этом проекте реализована с использованием шаблонов строк (string templates). По сути, проект просто парсит OpenAPI-схему и генерирует код валидаторов в виде строк. Этот подход достаточно прост, но имеет свои недостатки, о которых я расскажу ниже. 

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

Использование строк для генерации кода кажется простым и интуитивно понятным решением, но у этого метода есть особенности:

  1. Отсутствие статической типизации
    Работа с простыми строками означает, что целостность и корректность кода зависят только от разработчика. Нет инструментов для проверки ошибок на этапе компиляции.

  2. Сложность поддержки
    Код, созданный таким образом, труднее поддерживать и рефакторить из-за отсутствия встроенных механизмов проверки и подсказок от среды разработки (IDE).

На небольших проектах готовые решения могут закрыть многие потребности. Однако у нас было важное требований к генераторам – поддержка кастомных валидаторов. Разработка динамична, нужно добавлять и изменять кастомные валидаторы. Взять готовое решение не получится – нам в любом случае придется менять генераторы под себя. Поэтому, использование AST TypeScript может стать альтернативным решением проблемы. Работа с AST сложнее, но делает код более надежным и удобным в поддержке. В дальнейшем покажу, как можно автоматически генерировать AST из кода, что упростит работу с этим методом. А пока отмечу преимущества, которые мы получим, если будем использовать компилятор TypeScript для генерации кода:

Статическая типизация: код проверяется на этапе компиляции, что снижает вероятность ошибок.
Удобство работы: IDE будут корректно обрабатывать сгенерированный код, предоставляя автодополнение и другие полезные функции.

Создаем собственный генератор кода

Глобально, логика нашего решения следующая: 

  1. Бекенд автогенерирует файл OpenAPI

  2. После релиза бэка на CD автоматически собирается сайт со Swagger документации

  3. В рабочий чат приходит автоматическая отбивка, что бэк обновили

  4. Мы забираем с сайта OpenAPI файл к себе в репозиторий

  5. Запускаем cli команду, которая генерирует код валидаторов

  6. Забираем этот код к себе и собираем бандл через vite сборщик

Давайте разберем подробнее, как работает каждый из этапов. 

1. Автогенерация файла OpenAPI на бэкенде

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

2. CD-пайплайн собирает сайт с документацией

После завершения релиза бэкенда, пайплайн CI/CD автоматически:

  • Загружает последнюю версию спецификации OpenAPI.

  • Генерирует или обновляет сайт с документацией API (Swagger UI).

  • Оповещает в рабочий чат.

Когда процесс релиза завершается, бот отправляет сообщение в командный чат, информируя о том, что бэкенд обновлен. Сообщение также содержит ссылку на актуальную документацию и возможность скачать файл OpenAPI.

4. Загрузка OpenAPI файла в репозиторий

Фронтенд-разработчики скачивают обновлённый OpenAPI-файл с сайта документации и добавляют его в репозиторий проекта.

5. Генерация кода валидаторов через CLI

В проекте настроена CLI-команда, которая:

  • Читает OpenAPI файл.

  • Генерирует код валидаторов, соответствующих схемам из спецификации.

Этот процесс синхронизирует валидацию данных между фронтендом и бэкендом. Таким образом, генератор автоматически создает:

  • Модули валидаторов для каждого эндпоинта.

  • Типы данных для запросов и ответов.

  • Возможные текстовые сообщения для обработки ошибок.

  • Интеграция с проектом и сборка бандла.

  • После генерации валидаторы добавляются в проект. Сборщик (Vite), интегрирует их в общий бандл.

На этом этапе валидаторы становятся частью фронтенд-приложения. При запуске приложение автоматически проверяет входящие данные, используя те же правила, что и бэкенд.

Что еще нужно знать, чтобы успешно реализовать свой кастомный генератор? 

Парсинг OpenAPI-схемы

Для этого нужно использовать официальный пакет `@apidevtools/swagger-parser`, который загружает нужный нам файл и анализирует его. Это нужно для того, чтобы понять, какие url-адреса сервера есть, какие правила валидации для них заданы. 

Валидация данных через JSON схему в OpenAPI

Следующей проблемой становится факт, что в OpenAPI используются ref-ы, чтобы избежать дублирование информации. Нам нужно для каждого url-адреса сервера создать свой валидатор: для этого рекурсивно спускаемся по ссылкам и собираем схему.

Генерация из JSON схемы YUP валидатора

Как генерировать код TypeScript  на основе полученных данных? Для этого используем TypeScript AST Viewer. После ввода кода портал выдаст AST-дерево, куда надо подставить нужные значения. Компилятор TypeScript сгенерирует понятный код из полученного AST-дерева. Можно преобразовать код через Prietter, чтобы стиль не отличался от кодовой базы проекта.

Генератор схем имеет два режима работа. В первой группе файлов у нас генерируются все схемы для валидации данных. Во второй — словарь наших сообщений пользователю при возникновении ошибок. В этом режиме работы мы генерируем только саму схему, чтобы не перезаписать уже наполненный словарь с ошибками. Во время генерации кода мы запоминаем все полученные валидаторы и для каждого пишем в словарь соответствующий ключ. В своём коде разработчик всегда может расширить полученные валидаторы для своих форм.

У нас большое API, поэтому сгенерированный файл занимает более 2 тысяч строк кода. Мы используем code splitting и нам не хочется, чтобы такой большой объем кода попадал в бандл целиком. Мы генерируем несколько файлов и подключаем их на страницу по мере необходимости. Например, нет смысла на форме заказа подгружать валидаторы для формы добавления новостей и наоборот.

Особенности кастомных генераторов валидаторов

Следует понимать, предлагаемые решения – не серебряная пуля. 

Использование AST TypeScript позволяет работать не со строками и шаблонизаторами, а с типизированным кодом, который легко менять и дорабатывать. Это делает код более надёжным и удобным в поддержке. Но у такого подхода есть свои нюансы:

Необходимость самостоятельной поддержки кода

Например, если у вас простой сайт, то генераторы вам не понадобятся. Проще сделать мануально или использовать react-json-schema без лишней головной боли. Решение с генераторами, тем более кастомными, подходит для больших проектов, где нужен инструмент, который можно постоянно менять и дорабатывать под актуальные нужды. 

Высокий порог входа в AST TypeScript 

Для того, чтобы работать с кастомными генераторами, необходимо иметь понимание как работает TypeScript AST API. Для этого требуется время. В своей работе мы используем AST Viewer, который значительно упрощает понимание структуры кода. 

Время разработки

Написание генераторов требует времени. Нам, например, потребовалось две недели на реализацию решения. А затем еще две недели, чтобы довести решение до прода. 

Вместо заключения: что мы получили после написания своего генератора

Если кратко, то:

  • Нам удалось синхронизировать валидацию форм бэкенда и фронтенда.

  • Мы зафиксировали все ошибки валидации на проекте и передали менеджеру, чтобы для них были были подготовления сообщения.

  • У нас получилось сократить время на написание кода.

  • Вес нашего готового бандла уменьшился.

А вы готовы писать свои кастомные правила для eslint? Или проверять, что в названии коммита был добавлен номер тикета Jira с помощью кастомного правила для commit lint? Считаете ли вы, что кастомные генераторы кода имеют смысл в вашем проекте? Давайте обсудим в комментариях ниже!

Комментарии (2)


  1. vagon333
    22.11.2024 17:55

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

    Случайно не встречались с решением, которое на базе структуры данных в JSON генерит схему для Swagger и целиком Swagger версии 3.х?


    1. jarick Автор
      22.11.2024 17:55

      Встречались) chatgpt отлично это умеет делать.