Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Сегодня широкое распространение имеют следующие подходы для описания взаимодействия браузера и сервера, такие как OpenApi & GraphQL.
В этой статье я расскажу о нашей попытке сделать статически типизированное REST API и избавить фронтенд команду от написания кода по написания запросов данных, упростить тестирование и уменьшить количество возможных ошибок.
Эти инструменты помогают нам:
Основная идея подхода в том, что имея одинаковые типы данных на клиенте и сервере разработчикам в команде сложнее внести ошибку на клиенте, а используя генерацию кода его не нужно будет писать, сопровождать и соответственно покрывать юнит тестами.
Фронтенд приложение не скомпилируется если команда допустила ошибку в типах данных, которые принимает/поставляет REST API.
Таким образом имея статически типизированный код на клиенте, мы можем быть избавиться от глупых ошибок, связанных с типами и быть уверенными что наш код полностью совместим с текущей версией API.
Для того чтобы получить все вышеописанные преимущества статически типизированного API нам необходимо воспользоваться генератором кода, который по OpenAPI спецификации сможет сгенерировать файлы с описанием типов, для Typescript это *.d.ts файлы.
В нашем проекте используется микросервисная архитектура и весь бекенд написан на .NET поэтому для генерации клиентов API для фронтенда/бэкенда мы использовали NSwag. Этот инструмент позволяет генерировать OpenAPI документы, которые затем в свою очередь уже используются для генерации кода клиентов.
В общем случае процесс генерации кода состоит из нескольких этапов:
В нашем случае бекенд написан с использованием .Net и основной язык разработки C# этим обусловлен выбор инструментов. Мы для генерации OpenAPI документов и клиентов используем один и тот же инструмент под названием NSwag.
Рисунок 1 Архитектура решения по генерации кода rest клиентов
На рисунке обозначены:
Последовательность действий следующая:
Для того чтобы получить возможность генерации кода, нам необходимо описать типы принимаемых/возвращаемых значений контроллера API, в данном примере, где использован язык C#, с помощью атрибута SwaggerOperation мы пометили метод который будет возвращать список всех опросников на сервере, в коде клиентов метод для получения данных будет называться GetAllQuestionnaires:
Листинг 1 Пример С# кода, описывающего метод API
Затем с помощью NSwag мы автоматически генерируем OpenAPI документ, который будет содержать все API эндпоинты которые были помечены соответствующими атрибутами в коде бекенда.
Рисунок 2 OpenAPI документ
Таким образом у нас получилось создать всегда актуальную автоматически обновляемую документацию нашего API.
В OpenAPI документации имеется информация о типах данных, которые будут отправлены/приняты контроллером бекенда. Таким образом на стороне фронтенда можем полностью опираться на типы, которые поставляет нам бекенд и не создавать свои типы, а импортировать их из кода клиента, который был сгенерирован по OpenAPI документу.
Для нашего примера документ содержит информацию о типе QuestionnaireViewModel (здесь спецификация представлена в HTML виде для удобства чтения)
Рисунок 3 Пример модели данных в OpenAPI документе
Следующий шаг — это передать эту информацию в код фронтенд приложения.
Для генерации кода API клиента мы также используем NSwag. На вход он принимает OpenAPI документ и генерирует код API клиента в соответствии с заданными настройками. Для фронта рядом с полученным кодом мы добавляем package.json и отправляем в наш локальный npm регистр.
Как видно из листинга кода бэкенда (см. листинг 1), мы пометили метод контроллера с помощью атрибута
OperationId заданный в атрибуте C# в нашем случае станет именем метода клиента.
Рисунок 4 Пример использования сгенерированного API клиента
Также после генерации клиента мы получили d.ts файл который содержит соответствующие описания типов данных, показан на рисунке ниже.
Рисунок 5 Пример описания типа данных в .d.ts файле
Теперь в коде фронтенд приложения можно пользоваться типами данных, которые экспортируются из кода API клиента и пользоваться автодополнением в редакторе кода, пример показан на рисунке ниже.
Рисунок 6 Пример использования информации о типе данных API клиента
Также работают все соответствующие валидаторы типов данных в Typescript.
Пример на рисунках ниже.
Рисунок 7 Пример валидации типа данных API клиента
Рисунок 8 Пример валидации типа данных API клиента
После применения данного подхода мы получили следующие преимущества:
Ссылки
В этой статье я расскажу о нашей попытке сделать статически типизированное REST API и избавить фронтенд команду от написания кода по написания запросов данных, упростить тестирование и уменьшить количество возможных ошибок.
Эти инструменты помогают нам:
- Разрабатывать и моделировать API в соответствии со стандартами на основе спецификаций
- Создавать стабильный, многократно используемый код для вашего API практически на любом языке
- Улучшить опыт разработчика с помощью интерактивной документации API
- Легко проводить функциональные тесты на ваших API
- Создавать и применять лучшие рекомендации по стилю API в вашей API архитектуре
Основная идея подхода в том, что имея одинаковые типы данных на клиенте и сервере разработчикам в команде сложнее внести ошибку на клиенте, а используя генерацию кода его не нужно будет писать, сопровождать и соответственно покрывать юнит тестами.
Фронтенд приложение не скомпилируется если команда допустила ошибку в типах данных, которые принимает/поставляет REST API.
Таким образом имея статически типизированный код на клиенте, мы можем быть избавиться от глупых ошибок, связанных с типами и быть уверенными что наш код полностью совместим с текущей версией API.
Для того чтобы получить все вышеописанные преимущества статически типизированного API нам необходимо воспользоваться генератором кода, который по OpenAPI спецификации сможет сгенерировать файлы с описанием типов, для Typescript это *.d.ts файлы.
В нашем проекте используется микросервисная архитектура и весь бекенд написан на .NET поэтому для генерации клиентов API для фронтенда/бэкенда мы использовали NSwag. Этот инструмент позволяет генерировать OpenAPI документы, которые затем в свою очередь уже используются для генерации кода клиентов.
Архитектура
В общем случае процесс генерации кода состоит из нескольких этапов:
- Генерация OpenAPI документа
- Генерация кода по OpenAPI документу
В нашем случае бекенд написан с использованием .Net и основной язык разработки C# этим обусловлен выбор инструментов. Мы для генерации OpenAPI документов и клиентов используем один и тот же инструмент под названием NSwag.
Рисунок 1 Архитектура решения по генерации кода rest клиентов
На рисунке обозначены:
- Microservice 1...N — микросервисы предоставляющие API
- NSwag — генерация OpenAPI документа из кода API микросервиса (микросервис может быть написан на любом ЯП для которого есть инструмент для генерации документа OpenAPI)
- NSwag — генерация кода API клиента по OpenAPI документации (есть много инструментов можно выбрать тот, что больше подходит под ваш стек технологий)
- OpenAPI doc — OpenAPI документация сгенерированная
- API Client 1...N — клиенты потребители данных API (могут быть реализованы на любом ЯП, который поддерживает генератор, для NSwag C# и Typescript)
- SPA 1...N — Фронтенд приложение, в нашем случае React (NSwag поддерживает генерацию клиентов для AngularJS/Angular, React (fetch), JQuery(Promise/Callback), Aurelia)
Последовательность действий следующая:
- Пометить API контроллер соответствующим тегом/атрибутом/декоратором зависит от ЯП, на котором реализовано API
- Сгенерировать OpenAPI документацию по коду API
- Сгенерировать код API клиента по OpenAPI документации
- Интегрировать код API клиента в приложение потребитель данных API
Документация
Для того чтобы получить возможность генерации кода, нам необходимо описать типы принимаемых/возвращаемых значений контроллера API, в данном примере, где использован язык C#, с помощью атрибута SwaggerOperation мы пометили метод который будет возвращать список всех опросников на сервере, в коде клиентов метод для получения данных будет называться GetAllQuestionnaires:
[HttpGet, Route("")]
[SwaggerOperation(OperationId = "GetAllQuestionnaires")]
[SuccessResponse(typeof(IEnumerable<QuestionnaireViewModel>))]
public IEnumerable<QuestionnaireViewModel> Get()
{
var surveys = _questionnaireRepository.GetAll();
return surveys.Select(QuestionnaireViewModel.ToViewModel).ToArray();
}
Листинг 1 Пример С# кода, описывающего метод API
Затем с помощью NSwag мы автоматически генерируем OpenAPI документ, который будет содержать все API эндпоинты которые были помечены соответствующими атрибутами в коде бекенда.
Рисунок 2 OpenAPI документ
Таким образом у нас получилось создать всегда актуальную автоматически обновляемую документацию нашего API.
Типизация
В OpenAPI документации имеется информация о типах данных, которые будут отправлены/приняты контроллером бекенда. Таким образом на стороне фронтенда можем полностью опираться на типы, которые поставляет нам бекенд и не создавать свои типы, а импортировать их из кода клиента, который был сгенерирован по OpenAPI документу.
Для нашего примера документ содержит информацию о типе QuestionnaireViewModel (здесь спецификация представлена в HTML виде для удобства чтения)
Рисунок 3 Пример модели данных в OpenAPI документе
Следующий шаг — это передать эту информацию в код фронтенд приложения.
Генерация кода
Для генерации кода API клиента мы также используем NSwag. На вход он принимает OpenAPI документ и генерирует код API клиента в соответствии с заданными настройками. Для фронта рядом с полученным кодом мы добавляем package.json и отправляем в наш локальный npm регистр.
Как видно из листинга кода бэкенда (см. листинг 1), мы пометили метод контроллера с помощью атрибута
[SwaggerOperation(OperationId = "GetAllQuestionnaires")]
OperationId заданный в атрибуте C# в нашем случае станет именем метода клиента.
Рисунок 4 Пример использования сгенерированного API клиента
Также после генерации клиента мы получили d.ts файл который содержит соответствующие описания типов данных, показан на рисунке ниже.
Рисунок 5 Пример описания типа данных в .d.ts файле
Теперь в коде фронтенд приложения можно пользоваться типами данных, которые экспортируются из кода API клиента и пользоваться автодополнением в редакторе кода, пример показан на рисунке ниже.
Рисунок 6 Пример использования информации о типе данных API клиента
Также работают все соответствующие валидаторы типов данных в Typescript.
Пример на рисунках ниже.
Рисунок 7 Пример валидации типа данных API клиента
Рисунок 8 Пример валидации типа данных API клиента
Выводы
После применения данного подхода мы получили следующие преимущества:
- Меньше багов связанных с типами данных
- Меньше кода, меньше трудозатрат на отладку, тестирование, поддержку
- Всегда актуальная документация для всех методов API для каждого из микросервисов
- Возможность автоматизации тестирования API
- Единая система типов для фронтенда и бэкенда
Ссылки
- Спецификация OpenAPI
- Сайт проекта NSWAG