Русский
Русский
English
Статистика
Реклама

Openapi

Перевод Разработка Spring Boot-приложений с применением архитектуры API First

10.04.2021 16:09:58 | Автор: admin
В этом материале я приведу практический пример реализации архитектуры API First с применением спецификации OpenAPI. А именно, сначала расскажу о том, как создал определение API, а затем о том, как, на основе этого определения, создал серверную и клиентскую части приложения. В процессе работы у меня возникли некоторые сложности, которых я тоже коснусь в этом материале.


Преимущества архитектуры API First


По мере того, как всё сильнее распространяется подход к разработке приложений, основанный на микросервисах, определённую популярность набирает и архитектура API First. У этой архитектуры имеется довольно много сильных сторон.

Чёткое определение контрактов


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

Хорошее документирование API


Применение архитектуры API First способствует тому, что разработчик, создавая API, следует правилам, изложенным в хорошо структурированной, понятной документации. Это помогает тем, кому нужно работать с API, обрести чёткое понимание особенностей интерфейса, который представлен этим API.

Создание благоприятных условий для распараллеливания работы над проектами


Это одна из моих любимых возможностей, получаемых тем, кто применяет архитектуру API First. Если в проекте имеется чёткое описание API, то тот, кто занимается разработкой самого API, и тот, кто пользуется возможностями этого API, могут работать параллельно. В частности, пользователь API, ещё до того, как сам API готов к работе, может, основываясь на уже согласованных контактах, создавать моки и заниматься своей частью работы над проектом.

Теперь, когда мы обсудили сильные стороны архитектуры API First, перейдём к практике.

Практика использования архитектуры API First


Я начал работу с посещения ресурса, дающего доступ к Swagger Editor, и с создания определения моего API. Я, при описании API, пользовался спецификацией OpenAPI 3. Определения API можно создавать и с применением многих других инструментов, но я выбрал именно Swagger Editor из-за того, что у меня есть опыт создания документации для Java-проектов с использованием этого редактора. Swagger Editor мне нравится и из-за того, что он поддерживает контекстно-зависимое автодополнение ввода (Ctrl + Пробел). Эта возможность очень помогла мне при создании определения API.

Правда, я не могу сказать, что свободно владею техникой определения API, поэтому я, описывая свой API, опирался на пример petstore.

Для начала я создал весьма минималистичное определение API, описывающее создание учётной записи пользователя системы с использованием POST-запроса.


Определение API

Генерирование кода


После того, как подготовлено определение API, можно заняться созданием сервера и клиента для этого API. А именно, речь идёт о Spring Boot-приложении. Обычно я создаю такие приложения, пользуясь Spring Initializr. Единственной зависимостью, которую я добавил в проект, стал пакет Spring Web.

После этого я воспользовался Maven-плагином OpenAPI Generator, который позволяет создавать серверный и клиентский код.

Генерирование серверного кода


Для того, чтобы на основе определения API создать серверный код, я воспользовался соответствующим Maven-плагином, передав ему файл с описанием API. Так как мы создаём сервер, основанный на Spring, я, настраивая генератор, записал в параметр generatorName значение spring. Благодаря этому генератор будет знать о том, что он может, создавая серверный код, пользоваться классами Spring. Существует немало OpenAPI-генераторов серверного кода, их перечень можно найти здесь. Вот как выглядит конфигурационный файл плагина.


Содержимое конфигурационного файла для генерирования серверного кода

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

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

<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${springfox-version}</version></dependency><dependency><groupId>org.openapitools</groupId><artifactId>jackson-databind-nullable</artifactId><version>${jackson-databind-nullable}</version></dependency><dependency><groupId>io.swagger.core.v3</groupId><artifactId>swagger-annotations</artifactId><version>${swagger-annotations-version}</version></dependency>

А теперь самое интересное: генерирование кода. После выполнения команды mvn clean verify в нашем распоряжении оказываются несколько классов, сгенерированных плагином.


Результат работы генератора серверного кода

В пакете API имеются два основных интерфейса AccountApi и AccountApiDelegate. Интерфейс AccountApi содержит текущее определение API, оформленное с использованием аннотаций @postmapping. Там же имеется и необходимая документация API, представленная в виде аннотаций Spring Swagger. Классом, реализующим этот интерфейс, является AccountApiController. Он взаимодействует со службой, которой делегирована реализация возможностей, обеспечивающих работу API.

В нашей работе весьма важен интерфейс AccountApiDelegate. Благодаря ему мы можем пользоваться паттерном проектирования Delegate Service, который позволяет реализовывать процедуры обработки данных и механизмы выполнения неких действий, запускаемые при обращении к API. Именно в коде службы, созданной на основе этого интерфейса, и реализуется бизнес-логика приложения, ответственная за обработку запросов. После того, как будет реализована эта логика, сервер можно признать готовым к обработке запросов.

Теперь сервер готов к работе, а значит пришло время поговорить о клиенте.

Генерирование клиентского кода


Для генерирования клиентского кода я воспользовался тем же плагином, но теперь в параметр generatorName я записал значение java.


Содержимое конфигурационного файла для генерирования клиентского кода

Тут же я, в разделе configOptions, сделал некоторые настройки, в частности, касающиеся библиотеки, на которой должен быть основан REST-клиент. Здесь, для реализации механизма взаимодействия с сервером, я решил использовать библиотеку resttemplate. В разделе configOptions можно использовать и многие другие настройки.

Нам, кроме того, понадобятся дополнительные зависимости:

<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><dependency><groupId>io.swagger.core.v3</groupId><artifactId>swagger-annotations</artifactId><version>${swagger-annotations-version}</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${springfox-version}</version></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>${okhttp.version}</version></dependency><dependency><groupId>com.google.code.findbugs</groupId><artifactId>jsr305</artifactId><version>${jsr305.version}</version></dependency>


Результат работы генератора клиентского кода

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

Теперь надо создать экземпляр ApiClient. Он содержит параметры сервера, обмен данными с которым нужно наладить. Затем соответствующие возможности используются в классе AccountApi для выполнения запросов к серверу. Для того чтобы выполнить запрос, достаточно вызвать API-функцию из класса AccountApi.

На этом работа над клиентской частью проекта завершена. Теперь можно проверить совместную работу клиента и сервера.

Проблемы, выявленные в ходе реализации проекта


В последней версии плагина (5.0.1), которой я и пользовался, имеются некоторые проблемы.

  • При использовании генератора spring для создания контроллера в проект импортируются неиспользуемые зависимости из модуля Spring Data. В результате оказывается, что даже в том случае, если для организации работы соответствующей службы не нужна база данных, в проект, всё равно, попадают зависимости, предназначенные для работы с базами данных. Правда, если в проекте используется база данных, особых проблем вышеописанный недочёт не вызовет. Узнать о том, как продвигается работа по исправлению этой ошибки, можно здесь.
  • Обычно в разделе определения API components не описывают схемы запросов и ответов. Вместо этого в API используют ссылки на такие схемы, созданные с применением свойства $ref:. Сейчас этот механизм не работает так, как ожидается. Для того чтобы справиться с этой проблемой, можно описать встроенные (inline) схемы для каждого запроса и ответа. Это приводит к тому, что имена в модели генерируются с префиксом Inline*. Подробности об этой проблеме можно почитать здесь.

Если вы применяете более старую версию плагина, а именно 4.3.1, то знайте о том, что в ней этих проблем нет, работает она хорошо.

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

Применяете ли вы в своих проектах архитектуру API First?

Подробнее..

Создание самодокументирующегося сервера на Node.JS

20.01.2021 14:20:49 | Автор: admin

Условия:

  • валидация через Joi

  • использование Typescript

  • Express сервер

  • SWAGGER на /api-docs

Задача: DRY

Решение:

Для начала необходимо решить что первично: схема Joi, Swagger или TypeScript интерфейс. Эмпирическим путём установлено что первичной стоит сделать Joi.

1. Установка всех модулей на Express

npm install --save swagger-ui-express

Добавить строки в app.ts (index.ts):

import swaggerUI = require('swagger-ui-express')import swDocument from './openapi'...app.use('/api-docs',swaggerUI.serve,swaggerUI.setup(swDocument))

2. Создать ./openapi.ts

В этом файле содержится основные сведения о сервере. Создать его (как и все схемы, приведённые ниже) можно с помощью SWAGGER-утилиты. Важно выбрать при этом протокол openapi v3.0.0

Пример содержимого:

import {swLoginRoute} from './routes/login'const swagger = {  openapi: '3.0.0',  info: {    title: 'Express API for Dangle',    version: '1.0.0',    description: 'The REST API for Dangle Panel service'  },  servers: [    {      url: 'http://localhost:3001',      description: 'Development server'    }  ],  paths: {    ...swLoginRoute  },}export default swagger

Пути забираются из роутеров через инклуды.

3. Написать спецификацию роутера

В каждом роутере добавить openapi-описание

Пример ./routes/login/index.ts:

import {swGetUserInfo} from './get-user-info'import {swUpdateInfo} from './update-info'export const swLoginRoute = {  "/login": {    "get": {      ...swGetUserInfo    },    "patch": {      ...swUpdateInfo    }  }}

Выше описан путь /login, поддеживающий два метода: get и patch. Спецификации методов берутся инлудами из файлов get-user-into.ts и update-info.ts. Эти же файлы у меня содержат сами роуты.

4. Написать спецификацию роута и валидацию данных

Спецификация роута будет создаваться автоматически, на основе Joi-схемы.

Для начала сделаем инклуд будущей схемы в нашем роуте.

Примечание: совершенно не важно как вы располагаете ваши файлы, если соответственно модифицируете инклуды.

Строки из файла update-info.ts, в котором расположен мой роут (код код его самого нам не важен):

import schema, {joiSchema} from './update-info.spec/schema'export const swUpdateInfo = {  "summary": "update the user info",  "tags": [    "login"  ],  "parameters": [    {      "name": "key",      "in": "header",      "schema": {        "type": "string"      },      "required": true    }  ],  "requestBody": {    "content": {      "application/json": {        "schema": {          ...schema        }      }    }  },  "responses": {    "200": {      "description": "Done"    },    "default": {      "description": "Error message"    }  }}// ...далее идёт код роута

Этот JSON-объект можно сгенерить той же Swagger-утилитой, чтобы не мучать себя. Обратите внимание на следующую строку:

"schema": {  ...schema}

Это обеспечивает динамическое подключение нашей схемы.

Теперь можно добавить Joi-валидацию в роуте:

await joiSchema.validateAsync(req.body)

4. Пишем Joi-схему

Установка Joi:

npm install --save joi joi-to-swagger

Пример содержимого файла:

const joi = require('joi')const j2s = require('joi-to-swagger')// Joiexport const joiSchema = joi.object().keys({  mode:    joi.string().required(),  email:   joi.string().email()})// end of Joiconst schema = j2s(joiSchema).swaggerexport default schema

Данный файл осуществляет экспорт Joi-объекта и его swagger-схемы.

Чтож, на данном этапе у нас уже есть самодокументирующийся SWAGGER-сервер и валидация данных. Осталось настроить автоматическую генерацию TypeScript-интерфейсов

5. Генерация интерфейсов TypeScript

npm install --save-dev gulp @babel/register @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-typescript

Задачи на себя возьмёт Gulp. Это самая чувствительная часть системы, которую нужно настроить вручную под структуры вашего проекта. Вот как выглядит gulpfile.ts у меня:

const gulp = require('gulp')const through = require('through2')import { compile } from 'json-schema-to-typescript'const fs = require('fs')const endName = "schema.ts"const routes = `./routes/**/*.spec/*${endName}`function path(str: string) : string{   let base = str   if(base.lastIndexOf(endName) != -1)     base = base.substring(0, base.lastIndexOf(endName))   return base}gulp.task('schema', () => {  return gulp.src(routes)    .pipe(through.obj((chunk, enc, cb) => {      const filename = chunk.path      import(filename).then(schema => { // dynamic import        console.log('Converting', filename)        compile(schema.default, `IDTO`)          .then(ts => {            //console.log(path(filename).concat('interface.ts'), ts)            fs.writeFileSync(path(filename).concat('interface.ts'), ts)          })        })      cb(null, chunk)    }))})// watch serviceconst { watch, series } = require('gulp')exports.default = function() {  watch(routes, series('schema'))}

Скрипт обходит все подкаталоги с названием *.spec внутри каталога с роутера. Там он ищет файлы с именами *schema.ts и создаёт рядом файлы c именами *interface.ts

Заключение

Разумеется, эти большие и сложные JSON-объекты с openAPI-спецификацией пугают, но надо понимать, что они не пишутся вручную, а геренятся SWAGGER-утилитой.

Из-за неопытности первичная настройка механизма может занять какое-то время, но это приведет к экономии сотен часов, которые могли бы быть потрачены на рутину!

Подробнее..
Категории: Javascript , Node.js , Api , Express , Swagger , Openapi , Joi

7 вещей, которые нужно проработать, прежде чем запускать OpenShift в продакшн

24.12.2020 16:21:15 | Автор: admin
Взрывной рост использования контейнеров на предприятиях впечатляет. Контейнеры идеально совпали с ожиданиями и потребностями тех, кто хочет снизить затраты, расширить свои технические возможности и продвинуться вперед по пути agile и devops. Контейнерная революция открывает новые возможности и для тех, кто подзадержался с обновлением ИТ-систем. Контейнеры и Kubernetes это абсолютно и принципиально новый способ управления приложениями и ИТ-инфраструктурой.



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

Многие решают ускорить переход на контейнеры с помощью Red Hat OpenShift Container Platform, ведущей отраслевой Kubernetes-платформы для корпоративного сектора. Это решение автоматически берет на себя множество задач первого дня и предлагает лучшую Kubernetes-экосистему на базе единой, тщательно протестированной и высоко защищенной платформы. Это наиболее комплексное и функциональное решение для предприятий, которое содержит всё необходимое для начала работы и устраняет массу технических барьеров и сложностей при построении Kubernetes-платформы.

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

1. Стандартизация правил именования и метаданных


В компьютерных науках есть только две трудные вещи: аннулирование кэша и именование сущностей.
Фил Карлтон (Phil Karlton)

У всякой сущности в OpenShift и Kubernetes есть свое имя. И у каждого сервиса должно быть свое DNS-имя, единственное ограничение здесь правила именования DNS. А теперь представьте, что монолитное приложение разложилось на 100500 отдельных микросервисов, каждый с собственной базой данных. И да, в OpenShift всё является либо иерархическим, связанным, либо должно соответствовать шаблону. Так что именовать придется массу и массу всего. И если заранее не подготовить стандарты, это получится настоящий Дикий Запад.

Вы уже распланировали схему реализации сервисов? Допустим, это будет одно большое пространство имен, например, databases, в котором все будут размещать свои базы данных. OK, и даже допустим, что все так и будут делать, но потом-то они начнут размещать свои кластеры Kafka в своих собственных пространствах имен. Да, а нужно ли заводить пространство имен middleware? Или лучше назвать его messaging? И как обычно, в какой-то момент появляются ребята, которые всегда идут своим путем и считают себя особенными, и говорят, что им нужны собственные пространства имен. И слушайте, у нас же в организации 17 подразделений, может надо приделать ко всем пространствам имен наши стандартные префиксы подразделений?

Прежде чем пускать что-либо в продакшн, продумайте стандарты именования и сопоставления сэкономите массу времени и сил, если сделаете это заранее. Введите стандарты на всё. Причем, здесь важно не столько их качество, сколько наличие, целостность и выполнение.

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

2. Стандартизация корпоративных базовых образов


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

Возьмем, к примеру, базовое java-приложение. Ваши разработчики вряд ли ошибутся с выбором OpenJDK, а вот с управлением уязвимостями, обновлением библиотек и прочими вопросами ИТ-гигиены вполне могут. Мы все знаем, что бизнес-задачи зачастую решаются ценой технических компромиссов, вроде намеренного использования старых версий Java. К счастью, такие задачи легко автоматизируются и управляются на уровне предприятия. Вы по-прежнему может использовать базовые образы вендора, но одновременно задавать и контролировать свои циклы обновления, создавая собственные базовые образы.

Возвращаясь к примеру выше, допустим, разработчикам нужна Java 11, а вам, соответственно, надо, чтобы они всегда использовали самую последнюю версию Java 11. Тогда вы создаете корпоративный базовый образ (registry.yourcompany.io/java11), используя в качестве отправной точки базовый образ от вендора ОС (registry.redhat.io/ubi8/openjdk-11). А когда этот базовый образ обновляется, вы автоматом помогаете разработчикам задействовать последние обновления. К тому же, таким образом реализуется уровень абстракции, позволяющий бесшовно дополнять стандартный образ необходимыми библиотеками или Linux-пакетами.

3. Стандартизация проверок работоспособности и готовности


Контроль исправности, он нужен практически везде. Считается, что для человека достаточно ежегодного медосмотра. Исправность приложений надо проверять, понятно, гораздо чаще, и контролировать две ключевые вещи:

  • Запущено ли приложение (health check работоспособность).
  • Готово ли приложение (readiness check готовность).

Существует масса и других метрик, чтобы облегчить мониторинг приложений, но вот эти две это основа основ не только мониторинга, но и масштабирования. Работоспособность обычно определяется наличием сетевого подключения и способностью узла, на котором выполняется приложение, отозваться на запрос. Что касается готовности, то здесь уже каждое приложение должно реагировать на запросы по своим стандартам. Например, запуск приложения с очень низкими задержками может сопровождаться длительным обновлением кэша или прогревом JVM. И соответственно, пауза между ответами Запущено и Готово может достигать нескольких минут. А вот, например, для stateless REST API с реляционной базой данных эти ответы будут приходить одновременно.

Самое главное в этих проверках не отходить от сугубо двоичной логики. Запущено значит запущено, без всяких там как бы запущено. Готово значит готово, и никаких градаций, вроде на такие запросы готово отвечать, а на такие нет. Принцип простой: всё или ничего.

Второй аспект таких проверок это стандартизация. Как проверить готовность? Если у вас нет стандартов, то даже такой простой вопрос может стать настоящим кошмаром для мониторинга. Просто сравните, как разошлись друг от друга стандарты Quarkus и стандарты Spring Boot. А ведь никто этого не хотел, но со стандартами всегда так. Единственная разница в том, что теперь ваша организация сама имеет власть разрабатывать и вводить стандарты.
Примечание на полях. Не изобретайте свои стандарты. Просто найдите и используйте какой-нибудь готовый.

4. Стандартизация логов


Продолжая тему мониторинга, отметим, что сочетание недорогих хранилищ и решений класса big data породило на предприятиях нового монстра тотальное журналирование. Раньше это были неструктурированные и архаичные консольным логи, которые жили недолго и создавались от случая к случаю. Теперь норовят запротоколировать всё подряд и выстроить датасайнс с машинным обучением, чтобы самым революционным образом оптимизировать операции и мониторинг. Увы, надо признать очевидное: любые попытки начать собирать логи сотен приложений, не имея при этом абсолютно никаких стандартов и даже не задумываясь о них, неизменно приводят к бессмысленным и непомерным тратам на инструменты для управления логами и трансформации данных лишь для того, чтобы только начать работу. То есть еще до того, как вы поймете, что сообщения Выполнен переход или Этот блок сработал вряд имеют хоть какое-то отношение к вашим операциям.

Стандартизировать надо структуру. Повторимся: целостность стандартов важнее их правильности. Вы должны быть способы написать отдельный лог-парсер для каждого приложения, которое есть на предприятии. Да, это будут сугубо штучные, не тиражируемые вещи. Да, у вас будет куча исключений, которые нельзя контролировать, особенно для коробочных приложений. Но не выплескивайте ребенка вместе с водой, уделите внимание деталям: например, временная метка в каждом логе должна отвечать соответствующему стандарту ISO; сам вывод должен быть в формате UTC с точностью до 5-го знака в микросекундах (2018-11-07T00:25:00.07387Z). Уровни журнала должны быть оформлены CAPS-ом и там должны быть элементы TRACE, DEBUG, INFO, WARN, ERROR. В общем, задайте структуру, а уже затем разбирайтесь с подробностями.

Стандартизация структуры заставит всех придерживаться одних правил и использовать одни и те же архитектурные шаблоны. Это верно для логов как приложений, так и платформ. И не отклоняйтесь от готового решения без крайней нужды. EFK-стек (Elasticsearch, Fluentd и Kibana) платформы OpenShift должен быть в состоянии обработать все ваши сценарии. Он ведь вошел в состав платформы не просто так, и при ее обновлении это еще одна вещь, о которой не надо беспокоиться.

5. Переход на GitOps


Одна из главных прелестей OpenShift заключается в том, что здесь всё буквально: всё в конечном является либо конфигурацией, либо кодом, а значит, может контролироваться через систему управления версиями. Это позволяет революционизировать способы доставки и избавиться от бюрократии при запуске в продакшн.

В частности, традиционную схему на основе тикетов можно полностью заменить на модель с pull-запросами git. Допустим, владелец приложения хочет подкорректировать выделяемые приложению ресурсы после реализации в нем новых функций, например, увеличить память с 8 до 16 ГБ. В рамках традиционной схемы разработчику для этого надо создать тикет и ждать, пока кто-то другой выполнит соответствующую задачу. Этим кем-то другим чаще всего оказывается ИТ-эсплуатант, который лишь вносит ощутимую задержку в процесс реализации изменений, никак не повышая ценность этого процесса, или хуже того, навешивая на этот процесс лишние дополнительные циклы. В самом деле, у эсплуатанта есть два варианта действий. Первое: он рассматривает заявку и решает ее выполнить, для чего входит в продакшн-среду, вносит затребованные изменения вручную и перезапускает приложение.
Помимо времени на проведение самой работы здесь возникает и дополнительная задержка, поскольку у эксплуатанта, как правило, всегда есть целая очередь заявок на выполнение. Кроме того, возникает риск человеческой ошибки, например, ввод 160 ГБ вместо 16 ГБ. Второй вариант: эксплуатант ставит заявку под сомнение и тем самым запускает цепную реакцию по выяснению причин и последствий запрашиваемых изменений, да так, что иногда уже приходится вмешиваться начальству.

Теперь посмотрим, как это делается в GitOps. Запрос на изменения попадает в репозиторий git и превращается в pull-запрос. После чего разработчик может выставить этот pull-запрос (особенно, если это изменения в продакшн-среде) для утверждения причастными сторонами. Таким образом, специалисты по безопасности могут подключиться уже на ранней стадии, и всегда есть возможность отследить последовательность изменений. Стандарты в этой области можно внедрять программно, используя соответствующие средства в инструментальной цепочке CI/CD. После того, как его утвердили, pull-запрос версионируется и легко поддается аудиту. Кроме того, его можно протестировать в среде pre-production в рамках стандартного процесса, полностью устранив риск человеческой ошибки.

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

6. Схемы приложений (Blueprints)


Переход от монолитных приложений к микросервисам усиливает роль шаблонов проектирования (паттернов) приложений. В самом деле, типичное монолитное приложение не особо поддается классификации. Как правило, там есть и REST API, и пакетная обработка, и событиями оно управляется. HTTP, FTP, kafka, JMS и Infinispan? Да пожалуйста, а еще оно одновременно работает с тремя разными базами данных. И как прикажете создавать схему, когда здесь намешана целая куча шаблонов интеграции корпоративных приложений? Да никак.

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

  • REST API для управления данными в СУБД.
  • Пакетная обработка, которая проверят FTP-сервер на предмет обновления данных и отправляет их в топик kafka.
  • Camelадаптер, берущий данные из этого kafka-топика и отправляющий их в REST API
  • REST API, которые выдают обобщенную информацию, собираемую из Data Grid, которая действует как конечный автомат.

Итак, теперь у нас есть схемы, а схемы уже можно стандартизировать. REST API должны отвечать стандартам Open API. Пакетные задания будут управляться как пакетные задания OpenShift. Интеграции будут использовать Camel. Схемы можно создавать для API, для пакетных заданий, для AI/ML, для multicast-приложений, да для чего угодно. А затем уже можно определять, как развертывать эти схемы, как их конфигурировать, какие шаблоны использовать. Имея такие стандарты, не надо будет каждый раз изобретать колесо, и вы сможете лучше сфокусироваться на действительно важных задачах, вроде создания нового бизнес-функционала. Проработка схем может показаться пустой тратой времени, но затраченные усилия сторицей вернутся в будущем.

7. Подготовьтесь к API


Вместе с микросервисной архитектурой приходят и API. Ими тоже придется управлять и лучше подготовиться к этому заранее.

Во-первых, здесь опять понадобятся стандарты. В качестве отправной точки можно взять стандарты Open API, но придется углубиться в дебри. Хотя здесь важно соблюсти баланс и не впасть в чрезмерную зарегламентированность с кучей ограничений. Посмотрите на эти вопросы: когда новая сущность создается с помощью POST, что надо возвращать, 201 или 200? разрешается ли обновлять сущности с помощью POST, а не PUT? В чем разница между 400-ми и 500-ми ответами? примерно такой уровень детализации вам нужен.

Во-вторых, понадобится сервисная сетка service mesh. Это реально сильная вещь и со временем она станет неотъемлемой частью Kubernetes. Почему? Потому что трафик рано или поздно превратится в проблему, и вам захочется управлять им как внутри дата-центра (т.н. трафик восток-запад), так и между дата-центром и внешним по отношению к нему миром (север-юг). Вам захочется вытащить из приложений аутентификацию и авторизацию и вывести их на уровень платформы. Вам понадобятся возможности Kiali по визуализации трафика внутри service mesh, а также сине-зеленые и канареечные схемы развертывания приложений, или, к примеру, динамический контроль трафика. В общем, service mesh без вопросов входит в категорию задач первого дня.

В-третьих, вам понадобится решение для централизованного управления API. Вам захочется иметь одно окно для поиска и повторного использования API. Разработчикам понадобится возможность зайти в магазин API, найти там нужный API и получить документацию по его использованию. Вы захотите единообразно управлять версиями и deprecation-ами. Если вы создаете API для внешних потребителей, то такое решение может стать конечной точкой север-юг во всем, что касается безопасности и управления нагрузкой. 3Scale даже может помочь с монетизицией API. Ну и рано или поздно ваше руководство захочет получить отчет, отвечающий на вопрос Какие у нас есть API?.

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

Однако, надежда есть, и она заключается в автоматизации. Любой из перечисленных выше стандартов можно внедрить с помощью автоматизации. Процесс GitOps может проверять, что во всех соответствующих yaml-файлах присутствуют все требуемые метки и аннотации. Процесс CI/CD может контролировать соблюдение стандартов на корпоративные образы. Все может быть кодифицировано, проверено и приведено в соответствие. Кроме того, автоматизацию можно доработать, когда вы вводите новые стандарты или меняете существующие. Безусловное преимущество стандартизации через автоматизацию заключается в том, что компьютер не избегает конфликтов, а просто констатирует факты. Следовательно, при достаточной проработанности и инвестициях в автоматизацию, платформа, в которую вы вкладываете столько средств сегодня, может принести гораздо больший возврат инвестиций в будущем в виде повышения производительности и стабильности.
Подробнее..

SwaggerOpenAPI Specification как основа для ваших приёмочных тестов

02.11.2020 12:06:03 | Автор: admin

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


Я занимаюсь автоматизацией тестирования в Яндексе с 2013 года. Из них более четырёх лет автоматизирую тестирование REST API-сервисов. На Heisenbug я рассказал об использовании OpenAPI-спецификации как основы для приёмочных тестов, а также о том, как легко поддерживать автотесты на огромное количество REST API-сервисов и добавлять автотесты на новые проекты.



Под катом видеозапись и расшифровка моего доклада. Примеры из доклада есть на GitHub.



Как всё устроено


Яндекс.Вертикали это три больших сервиса: Яндекс.Недвижимость, Яндекс.Работа и Auto.ru. Они имеют микросервисную архитектуру. Большинство бэкендов это REST API-сервисы с разной кодовой базой, которые активно развиваются. К тому же у каждого REST API может быть несколько версий, которые также необходимо тестировать, чтобы старые клиенты не ломались при глобальных изменениях.


Команда


Наша команда это четыре-пять человек. Это люди, которые занимаются инструментами для автоматизации, инфраструктурой, пишут и встраивают автотесты в процесс разработки. Я занимаюсь также мобильным направлением: инфраструктурой для автоматизации тестирования под iOS и Android. В автотестах на клиенты мы активно используем моки, поэтому мы не можем позволить себе тестировать наш REST API через клиент.


Итого:


  • Несколько десятков бэкендов с разной кодовой базой, которые активно развиваются.
  • Маленькая команда автоматизации, состоящая всего лишь из четырёх-пяти человек.
  • Активное использование моков в тестировании.

С каким опытом мы подошли к нашей задаче


Три года назад картина у нас была следующая. У нас были автоматизаторы тестирования, которые имели достаточно стандартный подход и писали автотесты на Apache HTTP-клиенте. У нас были ручные тестировщики, которые не могли писать достаточно сложный код, поэтому использовали инструменты в виде Postman и писали автотесты, используя JavaScript. И у нас были разработчики, которые писали в основном юнит-тесты, интеграционные тесты. А некоторые вообще не понимали, зачем нужны автотесты, так как считали, что ничего не сломается.


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


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


В связи с этим мы сформировали определённые требования к автотестам:


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

Об эволюции автотестов на REST API


Необходимо понимать, с каким опытом мы пришли к этой задаче. Давайте поговорим об эволюции автотестов, которую мы прошли. Изначально мы писали автотесты на Apache HTTP client. Поняв, что дублируем много кода и он очень громоздкий, мы написали свою обвязку над HTTP client-ом. Это немного сокращало наши труды. Когда появились специализированные инструменты для автоматизации и появился REST Assured, мы начали его использовать. Потом мы осознали, что его тоже неудобно использовать, и написали свою обвязку над REST Assured. Всё это была эра клиента.


В какой-то момент мы поняли, что очень часто дублируем bean-ы для реквестов, для респонсов и решили их генерировать из JSON Schema. Это оказалось очень удобно: у нас переиспользуется код. Код упростился, и нам это очень понравилось. Стало ясно, что можно генерировать не только bean-ы, но и из них генерировать assertion-ы и получать типизированные assertion-ы для этих bean-ов.


Позже мы поняли, что можно генерировать не только bean-ны, assertion-ы, но ещё и тестовый клиент. Мы стали генерировать клиент на основе RAML-спецификации. Это тоже экономило много времени и делало клиент единообразным. У нас уменьшалось время внедрения людей в новый проект автотестов. Затем мы решили не генерировать bean-ы, а сразу брать их напрямую из кода и генерировать их в проекте автотестов. Мы назвали это эрой кодогенерации.



Ещё очень важный момент. У нас во всех проектах есть спецификации. В основном это спецификации двух версий это OpenAPI-спецификации v1.0 и OpenAPI-спецификации v2.0. В какой-то момент пришёл менеджер и сказал, что мы больше не будем релизить новые REST API-сервисы без спецификаций.
Зачем мы вообще используем спецификацию? Всё благодаря этой замечательной странице Swagger UI.



Из неё нам понятно, какой перед нами API, понятны все операции, понятно, как API используется и что вернётся. Это экономит огромное количество времени разработке для коммуникации с фронтендерами, разработчиками мобильных приложений, с ребятами, которые занимаются клиентами. Более того, через эту страницу можно делать запросы и получать ответы. Это оценили наши тестировщики, которые могут, не используя Curl, тестировать релиз. Исходя из этого мы решили, что будем строить наши автотесты на основе кодогенерации, и в качестве основы мы возьмём Swagger/OAS.


Мы решили строить такой процесс: у нас будет REST API, из него мы будем получать OpenAPI-спецификацию, а затем из OpenAPI-спецификации тестовый клиент, с помощью которого мы и будем писать автотесты.


Что такое OpenAPI-спецификация


OpenAPI-спецификация это opensource-проект, описывающий спецификацию и поддерживаемый линукс-сообществом (Linux Foundation Collaborative Project). Это популярный проект, у него 16000 звёздочек на GitHub.


OpenAPI-спецификация определяет стандартизированное описание для REST API-сервисов, независимо от того, на каком языке программирования они написаны, удобна для использования как человеком, так и компьютерной программой, не требует доступа к коду. Спецификация может быть двух форматов. Это может быть JSON, которая понятна для машин и не очень понятна для человека и YAML-спецификация, которая более или менее читаема для человека.


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



Наш Swagger UI и строится на основе этого файла спецификации swagger.json.



Как получить OpenAPI-спецификацию


Самый простой способ написать её в текстовом файлике. Это долго и не очень удобно.


Второй способ использовать специализированные средства для написания спецификаций. Например, Swagger Editor. Вы в нем описываете вашу спецификацию, там есть удобный редактор, который сразу её валидирует. В правой части она у вас отображается в красивом виде.



Эти два инструмента полезны, когда у вас нет кода. Вы можете сначала написать спецификацию, потом на ее основе написать код.


Есть третий способ для получения спецификации: через Swagger-annotation в коде. Допустим, у вас есть какой-то API-ресурс, вы описываете для него Swagger-annotation. Annotation processor обработает аннотации вашего сервиса и вернёт спецификацию. Для каждого релиза вашего REST API получаем всегда актуальную OpenAPI-спецификацию. Если вы что-то удаляете, у вас автоматически это удаляется из OpenAPI-спецификации. И этот процесс постоянный.



Генерация клиента


Давайте разберёмся теперь с генерацией клиента. В opensource есть два больших, достаточно популярных проекта. Это Swagger Codegen, он на данный момент поддерживается компанией SmartBear. У него 11000 звёздочек на GitHub. И OpenAPI Generator, тоже opensource-проект, но он поддерживается комьюнити. В основном про него я и буду говорить.


По факту OpenAPI Generator является форком Swagger Codegen. Он отбренчевался от этого проекта в 2018 году.


Это произошло в связи с независимым развитием Swagger Codegen 3.X и Swagger Codegen 2.X. Из-за этого нарушилась обратная совместимость. Очень много клиентов исчезли и не были поддержаны. И ещё одна причина это нестабильность релизного цикла. Релизы в Swagger Codegen были довольно редкие, тесты часто падали и комьюнити это не устраивало.


Давайте сгенерируем какой-нибудь клиент. Если у вас есть Docker, вы просто выполняете команду, указываете путь до спецификации, указываете язык, на котором хотите получить клиент, и папку для результата.



Есть второй способ: устанавливаете себе локальную консольную программу, выполняете команду и получаете клиент.


Вы получаете готовый проект. Там есть и клиент, и тест, даже скрипт, который пушит код на GitHub. По факту этот проект уже можно использовать. Но как только вы его начнёте использовать, то поймёте, что что-то идёт не так. Ниже реальный пример, где я использовал генерацию кода.



На самом деле мы пытаемся генерировать клиент из спецификации, которая на это не рассчитана. Данная спецификация использовалась только для Swagger UI, а мы хотим получить клиент. И мы можем получить что-то невразумительное. Вместо методов вашего тестового клиента у вас будут route1, route2, route16.


Проблемы, с которыми мы столкнулись при генерации


Также вы получите другие различные проблемы. Например, опечатки, потому что Swagger Annotation пишется руками разработчиков. Опечатки можно поправить это не проблема. Могут быть различные проблемы с повторением моделей. Это достаточно легко решается, если к модели добавить имя пакета. И ещё одна проблема неполнота спецификаций. Скоро вы обнаружите, что в вашем API есть внутренние операции, о которых вы не знали, но которые тоже надо тестировать. Самое приятное, что всё это легко исправляется.


Но есть тонкий момент этот клиент и весь проект мы получаем один раз во время генерации. То есть проблема осталась: напомню, что при любом изменении REST API нам придётся снова поддерживать тестовые клиенты. Тогда мы решили, что будем генерировать клиент до запуска тестов и будем делать это с помощью плагина. OpenAPI Generator поддерживает множество плагинов. Например, это maven-plugin, gradle-plugin, sbt-plugin и bazel-plugin. В качестве примера я возьму maven-plugin.


Мы добавляем в наш pom.xml maven-plugin с определёнными настройками, указываем путь к нашей спецификации, папку для результата, язык генерации, dateLibrary, флаги валидировать вашу спецификацию или нет во время генерации, генерировать ли ваш клиент, если спецификация не менялась.



После компиляции у нас получается готовый клиент в target. Его можно использовать. По факту мы получили постоянный процесс: прямо из REST API с помощью Swagger-annotation мы получаем OpenAPI-спецификацию; из OpenAPI-спецификации мы получаем наш тестовый клиент.


Что делать, если у REST API несколько версий?


Например, есть версия v1.x, v2.x и vN.x. На самом деле всё то же самое. Мы в наш плагин добавляем второй . То есть у нас получается два , для версии v1 и для версии v2.


После компиляции у нас происходит генерация клиента для версии v1 и генерация клиента для версии v2.


Как добавляли клиент


Вернёмся в 2018 год. Когда мы только всё начинали, мы рассматривали множество клиентов в Swagger Codegen, написанных под разные языки, но ни один нас не устроил. Все эти клиенты очень жёстко привязаны к документации. В них мало точек расширения, и они не рассчитаны на то, что наша спецификация будет меняться. Мы решили, что напишем свой API-клиент, который будет обладать всеми необходимыми для нас возможностями.


В качестве библиотеки мы выбрали REST Assured. Она имеет fluent interface, эта библиотека предназначена для тестирования. В ней есть механизм Request specification и Response specification.


Сама генерация клиента в OpenAPI Generator основана на mustache template (Logic-less Mustache engine). Это круто, потому что генерация не зависит от языка программирования. Вы можете использовать mustache-шаблоны как для C#, так и для С++, так и для любого языка и получить клиент. Ещё один плюс эти клиенты легко добавлять. Вам надо только добавить набор mustache-шаблонов, и у вас готовый клиент. И третья очень крутая фича эти клиенты очень легко кастомизировать. Достаточно добавить свои шаблоны, которые просто будут использоваться при генерации.


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


Аналогично можно получить те же переменные, но уже для моделей.


Итак, мы получили все переменные, написали все шаблоны и соответственно сделали pull request в Swagger Codegen. И этот pull request приняли. Теперь у нас есть собственный клиент.


Давайте рассмотрим его подробнее. Возьмём в качестве примера простейшую операцию GET /store/Inventory из Story API и попробуем написать тест. Мы будем делать простейший запрос без параметров и валидировать ответ.



Retrofit


В качестве библиотеки для сравнения я возьму готовую библиотеку Retrofit, которая есть в OpenAPI Generator и Swagger Codegen. Так выглядит код теста, написанного на Retrofit.



Здесь есть создание клиента: OkHttp сlient из него билдится и настраивается Retrofit-клиент. Это простой тест: мы берём и проверяем, что конкретно у этого запроса статус OK и количество элементов больше нуля.


Давайте рассмотрим тест подробнее. Здесь api. входная точка на наш клиент.



Здесь Story API, мы вызываем метод getInventory и его выполняем.



Здесь же валидация кода ответа. Мы просто проверяем, что код 200.


REST Assured


Давайте напишем тот же тест, но уже на REST Assured. Вот у нас идёт создание клиента, мы его настраиваем, устанавливаем config, устанавливаем mapper, добавляем filter, добавляем BaseUri.



Это простейший тест, он чуть поменьше. Давайте его рассмотрим поподробнее. Что здесь происходит? Есть API-клиент, есть вызов Story API и метод getInventory.



Далее мы используем response specification это особенность REST Assured. И валидируем сразу ответ, проверяем, что код 200.



Два примера очень похожи. Возьмём пример посложнее. Возьмём ручку search.



Пусть у неё будет множество параметров. Метод, который получится в Retrofit, будет выглядеть вот так.



Когда вы начнёте писать тест, то получите тест с множеством null. Он не очень понятен.


В том же REST Assured мы использовали builder-паттерн, и у каждого вызова параметра собственный метод. И тест на REST Assured будет выглядеть вот так.



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



В REST Assured ошибки не будет, потому что у нас builder-паттерн. Проблемы не возникает.


В стандартном Retrofit-клиенте параметры типизированы, для тестирования это не очень удобно. Нам бы хотелось, чтобы параметры были не типизированы, и мы могли вставить любые параметры, получить ошибку и её валидировать.


В REST Assured нетипизированные параметры. У Retrofit ответ всегда мапится на ответ из спецификации по умолчанию. В REST Assured ответ может мапиться на ответ из спецификации, а может не мапиться. Потому что, если мы тестируем невалидные кейсы, например, статус-коды не 200, то нам бы хотелось как-то кастомизировать наш ответ, чтобы получать что-то невалидное.


Retrofit не очень удобно кастомизировать. Например, если мне захочется добавить какой-то хедер, то в Retrofit это сделать не очень удобно. В REST Assured у нас есть механизм Request Specification, которым вы можете на любом этапе кастомизировать ваш запрос: как на этапе группы операций, так и на этапе самого запроса.


В данном случае я просто добавил к нашему запросу header x-real-ip.



Вообще, это полезно иметь для любого тестового клиента:


  • Билдер для клиента, чтобы у нас тесты проще писались и не ломались.
  • Нетипизированные параметры, чтобы писать тесты на кейсы с невалидными параметрами.
  • Кастомизацию маппинга ответа.
  • Кастомизацию запроса.

REST Assured сlient и Retrofit есть в OpenAPI Generator, их можно попробовать и использовать.


В Swagger Codegen с переходом на другой template-engine остался один Retrofit-клиент. REST Assured-клиента на данный момент нет, но есть открытое issue на его возвращение.


Тяжело ли поддерживать клиент


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


Например, рассмотрим редкий случай. Допустим, у нас есть e-num с элементами c одинаковым префиксом. Есть enum, состоящий из следующего: PREFIX_SOLD, PREFIX_AVAIBLE, PREFIX_PENDING.


После генерации сам генератор вырезает этот префикс, и мы уже в тестах используем enum без префикса: SOLD, AVAILABLE, PENDING. Если мы в e-num добавляем значение RETURNED, то после генерации произойдёт следующее: PREFIX возвращается, и наши тесты, которые использовали этот enum без префикса, ломаются. Ломается компиляция. Это первая ситуация за два года, в которой я столкнулся с тем, что когда что-то добавляется, то ломается компиляция.


Удаление или изменение спецификации может сломать компиляцию клиента. Здесь более или менее всё понятно. Допустим, удаляем параметр status. Мы этот параметр используем в тесте, но его уже нет в клиенте. Получаем ошибку компиляции.



Ещё вариант поиск с множеством параметров. Поменяем pet на search. Этого метода в API api.pet нет и получаем ошибку компиляции. Что вполне ожидаемо.



И ещё: изменим в спецификации path. У нас был path /pet/search/, а мы его поменяем на /search/pet/. Что будет?



Поменяется только одна константа внутри нашей операции. И получится, что ошибок компиляции нет. Это неожиданно и ломает все представления об автотестах. Они должны ловить такие случаи, но сейчас получается, что всё работает. Чтобы отлавливать такие случаи, мы используем diff-спецификации, о которых чуть позже.


Несмотря на всё это, мы получили одну важную вещь: правим автотесты тогда, когда меняется бизнес-логика. Более того, у нас всегда актуальный клиент.


Про тесты и другие возможности


Здесь важно следующее: чем подробнее у нас спецификация, тем больше у нас возможностей.


Например, у наших спецификаций есть модели. Мы можем попробовать сгенерировать assertions на модели ответов с помощью плагина. Получается генерация в кубе. У вас есть в коде модели, эти модели переносятся в OpenAPI-спецификацию, из OpenAPI-спецификации у нас генерятся bean-ы и на эти bean-ы мы генерим assertions.


Например, мы возьмём в качестве библиотеки для генерации assertion такую популярную библиотеку как AssertJ. В AssertJ есть два плагина: есть плагин под Maven, есть плагин под Gradle. С помощью плагина получаем типизированный assertion, который можно использовать.


После настройки этого плагина мы просто указываем пакет, где у нас сгенерированные модели.



Вместо кода, где у нас не типизированные assertions, а стандартные hamcrest матчеры, мы получаем типизированные assertions более удобные и понятные. Если у нас кроме модели ещё есть примеры значений параметров, то можем попробовать сгенерировать реальные шаблоны тестов и сами тесты. Нужно добавить парочку темплейтов, и получим тесты.


Нам пришла идея, почему бы не использовать свои темплейты. Тогда мы получим шаблоны тестов, которые можно использовать и писать. Значения в OpenAPI-спецификации второй версии хранятся в поле x-exаmple. На это поле нет жёстких ограничений.


Какие тесты можно сгенерировать


Для того чтобы настроить генерацию тестов, нам надо прописать template_directory с нужными темплейтами и добавить шаблон для тестов.


Я буду рассматривать контрактные тесты и тесты на сравнение.


Давайте поговорим про контрактные тесты. Мы можем генерировать тесты на статус-коды, можем генерировать тесты на модели и тесты на параметры. Это почти всё, что у нас есть в спецификации по контракту. Здесь я привёл в пример mustache шаблон для генерации теста на статус-коды. Давайте чуть подробнее его рассмотрим. Мы проходим по всем респонсам, пишем бизнес-логику. Я ещё указал Allure-аннотации. Также делаем шаблон для имени теста. Мы устанавливаем в переменные нужные нам значения из спецификации и прокидываем их в вызов реквестов с тестового клиента. В конце добавлена валидация: проверка, что статус код 200.



После генерации у нас в папке target возникают такие тесты. Это реальный тест на 200, его можно запустить.



Код 404 будет выглядеть точно так же. Основная идея, что mustache-темплейты logic-less, и тяжёлую логику нельзя таким образом сгенерировать. И потому это не совсем реальные тесты, а шаблоны тестов. Но дописав логики, вы получите вполне реальные тесты.


Аналогично можно сгенерировать тесты для параметров и моделей. Для параметров происходит то же самое. Вы просто проходите по всем параметрам и добавляете шаблоны тестов, чтобы для каждого параметра сгенерировались аналогичные тесты.


С моделями всё интересней. Мы можем проверять в тестах, что наш ответ после запроса соответствует определённой модели, которая описана в спецификации. Но в реальности это очень маленькое, узкое покрытие. Мы проверяем только модели и не проверяем значения. Если это, например, JSON, то мы проверяем только поля и что они соответствуют схеме. Но нам бы хотелось понимать, что ответ нашего запроса правильный.


Я обычно предпочитаю тесты на сравнение. Что это и как это работает? Допустим, у нас REST API, который мы тестируем. Рядом поднимаем тот же REST API, но со стабильной версией, о котором мы знаем, что он работает правильно. Делаем два запроса: запрос к REST API, который тестируем, и тот же запрос к стабильному REST API. Получаем два ответа. Первый ответ нам нужно протестировать, второй ответ мы считаем эталоном expected response. Сравнивая два ответа, можем проанализировать, правильно всё работает или нет.


Давайте попробуем написать это в виде шаблонов. Для этого я привёл пример параметров для такого теста. У нас есть механизм в REST Assured Response и Request specification. Здесь параметром выступает Request specification. Мы устанавливаем в Request specification все возможные параметры со значениями, которые описаны у нас в спецификации.



Здесь пример теста, который просто сравнивает эти два ответа. У нас есть функция, которую принимает API-клиент и возвращает ответ, который мы сравниваем через matcher jsonEquals.



Тест после генерации будет выглядеть так. Здесь значения x-example такие же, как и в спецификации. И это получается вполне реальный тест. Осталось добавить реальных тестовых данных и можно его использовать.



Как в итоге мы стали писать тесты


У нас генерируются тестовые классы, мы можем поправить их в target, запустить и использовать. Через IDE нажимаем клавишу F6, и у нас возникает окошко.



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


Что мы получили:


  • Простоту добавления новых тестовых классов. Мы можем легко перенести их в нужные нам тесты и использовать.
  • Единые наименования тестовых методов и классов.

Для тестировщиков головная боль именование тестовых методов и тестовых классов. У нас единое наименование на основе OpenAPI-спецификации. Из-за этого единообразия мы пришли к тому, что наши автотесты могут писать все. Их могут писать автоматизаторы тестирования и понимать ручные тестировщики и разработчики. Всю эту красоту мы вынесли в шаблон проекта с автотестами.


На GitHub создали project-template, где указали клиент, модуль с тестами, настроили генерацию. Если нам нужен проект автотестов на какой-то новый сервис, мы берём и наследуемся от этого шаблона. Чтобы проект заработал, нам достаточно поменять значения двух property, и получаем готовые тесты и сгенерированные клиенты.



По факту мы получили фабрику автотестов, используя которую стало легко добавлять автотесты на любой проект.


Об инструментах


Давайте поговорим об инструментах.


Бывает так, что проект автотестов не компилируется из-за изменений спецификации сервиса. Необходимо понимать, почему это происходит. Для этого нам надо получить разницу в документации, например, используя swagger-diff.


Swagger-diff это opensource-проект, который помогает сравнить две спецификации. Я взял его в качестве примера, но есть и другие проекты.


В качестве сравнения берём старую и новую спецификацию: из стабильной версии REST API и тестовой. Далее сравнивая их, мы получаем diff в виде HTML.
Из HTML видим, что удалили параметры search, status и добавили header x-geo.



Из HTML понятно, что изменилось.
Мы также можем анализировать обратную совместимость. Выявлять случайное удаление параметров и другие изменения.


Swagger-coverage. Как его использовать?


С течением времени количество проектов стало расти, REST API постоянно менялись. При этом наши REST API имеют большое количество операций. В самом большом REST API, который я покрывал автотестами, было около 700 операций. Наши автотесты пишут все члены команды: ручные тестировщики, разработчики, автоматизаторы. Ещё мы наняли стажёров, которые тоже пишут тесты и которых тоже нужно контролировать. Возникает вопрос, как вообще понять, что покрыто тестами, а что нет?


Мы решили, что нам нужен инструмент, который позволит измерить это покрытие на основе OpenAPI-спецификации. Я хочу рассказать про Swagger-coverage, который мы недавно реализовали.


Как он работает? У вас есть исходная спецификация. Для каждого реквеста вы можете получить информацию в формате OpenAPI-спецификации. Далее мы агрегируем всю информацию из реквестов, сравниваем с исходной спецификацией и получаем coverage.


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



На странице есть фильтры. Мы знаем, какие операции у нас полностью покрыты, какие частично, какие вообще не покрыты. Также, если мы рассмотрим подробнее операцию, то увидим условия. Эти условия формируются на основе OpenAPI-спецификации. Например, если в OpenAPI-спецификации есть пять кодов ответа, то сформируется пять условий на эти коды ответа. В нашем примере видим, что у нас не проверяются все значения параметра status, мы не используем значение enum-а sold.



Все эти условия настраиваются через Config JSON.


Так же выглядят и теги это группа операций. Мы можем понять, сколько операций покрывается, сколько вообще не покрыты. Ещё есть фильтры по условиям, можем посмотреть, какие условия не покрываются совсем.


Зачем это нужно, и как это использовать? Есть несколько способов использования.


  • Анализировать прогресс автоматизации: нам нужно понимать, как и что мы покрываем тестами.
  • Выбрать стратегию автоматизации. Например, покрывать то, что вообще не покрыто. Или сосредоточиться на параметрах и на операциях, которые хотя бы как-то покрыты.
  • Отслеживать добавление новых сущностей без тестов.

Как построить процесс


Теперь давайте построим процесс, объединив всё в одну большую систему.


У нас есть два инструмента: Swagger-diff и Swagger-coverage. Swagger-diff возвращает json с диффом, Swagger-coverage тоже возвращает json о покрытии.


Мы можем сделать такие выводы: что-то изменилось и покрыто тестами или что-то изменилось, но не покрыто тестами.


Мы можем красить build в красный цвет и тем самым стимулировать разработчиков писать тесты.


Как построить процесс? У нас собрался build, и запустился Swagger-diff, и мы уже заранее знаем на этом этапе, сломалась ли обратная совместимость и изменилось ли что-нибудь. Это же может показать запуск тестов. Потом мы можем по результатам тестов построить coverage, чтобы понять, что покрыто, а что нет, и на основе этого выбирать дальнейшую стратегию тестирования.


Подведём итог


Что такое Swagger/OAS как основа приёмочных тестов?


  • Всегда актуальный тестовый клиент, понятный для всех членов команды
  • Сгенерированные тесты и шаблоны тестов
  • Лёгкое добавление автотестов на новые проекты, вне зависимости от языка программирования
  • Прозрачность покрытия и изменений REST API через прозрачный инструментарий.

Минутка рекламы: читать расшифровки докладов по тестированию интересно, но ещё интереснее смотреть новые доклады в прямом эфире с возможностью задать вопрос спикеру. Уже на днях, 4 ноября, стартует Heisenbug 2020 Moscow и там докладов по тестированию будет сразу множество.
Подробнее..

Автоматическая документация для Flask с использованием OpenAPI

15.02.2021 18:22:10 | Автор: admin
image alt


Техническая документация, как известно, крайне важная часть любого проекта. До недавнего времени мы прекрасно жили с таким генератором документаций как Sphinx. Но наступил момент переходить на технологии с бОльшим набором возможностей, поэтому мы приняли решение переписать нашу документацию на более современный стандарт: OpenAPI Specification. Эта статья является скромным гайдом по такому переезду. Она будет интересна Python-разработчикам, особенно тем, которые используют Flask. После ее прочтения вы узнаете, как создать статическую OpenAPI документацию для Flask приложения и развернуть ее в GitLab Pages.




apispec + marshmallow


В качестве веб-фреймворка у нас используется Flask. Документацию для API, созданного с помощью него, мы и хотим создать. Спецификация по стандарту OpenAPI описывается форматом YAML(или JSON). Чтобы преобразовать докстринги нашего API в необходимый формат, будем использовать такой инструмент, как apispec, и его плагины. Например, MarshmallowPlugin, с помощью которого (и самой библиотеки marshmallow) можно удобно за счет возможности наследования и переиспользования описать входные и выходные данные эндпоинтов в виде python классов, а также провалидировать их.

Используя библиотеку marshmallow, создадим класс, описывающий параметры API:

from marshmallow import Schema, fieldsclass InputSchema(Schema):   number = fields.Int(description="Число", required=True, example=5)   power = fields.Int(description="Степень", required=True, example=2)

Аналогично сделаем для выходных параметров:

class OutputSchema(Schema):   result = fields.Int(description="Результат", required=True, example=25)

Для группировки запросов в OpenAPI используются теги. Создадим тег и добавим его в объект APISpec:

def create_tags(spec):   """ Создаем теги.   :param spec: объект APISpec для сохранения тегов   """   tags = [{'name': 'math', 'description': 'Математические функции'}]   for tag in tags:       print(f"Добавляем тег: {tag['name']}")       spec.tag(tag)

Далее нам нужно интегрировать параметры в докстринг так, чтобы это соответствовало OpenAPI спецификации.

Пример:

from flask import Blueprint, current_app, json, requestblueprint_power = Blueprint(name="power", import_name=__name__)@blueprint_power.route('/power')def power():   """   ---   get:     summary: Возводит число в степень     parameters:       - in: query         schema: InputSchema     responses:       '200':         description: Результат возведения в степень         content:           application/json:             schema: OutputSchema       '400':         description: Не передан обязательный параметр         content:           application/json:             schema: ErrorSchema     tags:       - math   """   args = request.args   number = args.get('number')   if number is None:       return current_app.response_class(           response=json.dumps(               {'error': 'Не передан параметр number'}           ),           status=400,           mimetype='application/json'       )   power = args.get('power')   if power is None:       return current_app.response_class(           response=json.dumps(               {'error': 'Не передан параметр power'}           ),           status=400,           mimetype='application/json'       )   return current_app.response_class(       response=json.dumps(           {'response': int(number)**int(power)}       ),       status=200,       mimetype='application/json'   )

Эта функция пример реализации метода GET в нашем API.

Блок summary. Краткое описание функции. Для более подробного описания можно добавить блок description.

Блок parameters. Описание параметров запроса. У параметра указывается, откуда он берется:

  • path, для /power/{number}
  • query, для /power?number=5
  • header, для X-MyHeader: Value
  • cookie, для параметров переданных в cookie файле

и schema, в которую передается python класс, описывающий данный параметр.

Блок responses. Описание вариантов ответа команды и их структура.

Блок tags. Описание тегов, которые используются для логической группировки эндпоинтов.

Для POST запроса, например, можно указать еще requestBody, в котором описываются параметры, передаваемые в теле. Подробнее можно почитать в официальной документации.

После того, как мы описали методы API, можем загрузить их описание в объект APISpec:

def load_docstrings(spec, app):   """ Загружаем описание API.   :param spec: объект APISpec, куда загружаем описание функций   :param app: экземпляр Flask приложения, откуда берем описание функций   """   for fn_name in app.view_functions:       if fn_name == 'static':           continue       print(f'Загружаем описание для функции: {fn_name}')       view_fn = app.view_functions[fn_name]       spec.path(view=view_fn)

Создаем метод get_apispec, который будет возвращать объект APISpec, в нем добавляем общую информацию о проекте и вызываем описанные ранее методы load_docstrings и create_tags:

from apispec import APISpecfrom apispec.ext.marshmallow import MarshmallowPluginfrom apispec_webframeworks.flask import FlaskPlugindef get_apispec(app):   """ Формируем объект APISpec.   :param app: объект Flask приложения   """   spec = APISpec(       title="My App",       version="1.0.0",       openapi_version="3.0.3",       plugins=[FlaskPlugin(), MarshmallowPlugin()],   )   spec.components.schema("Input", schema=InputSchema)   spec.components.schema("Output", schema=OutputSchema)   spec.components.schema("Error", schema=ErrorSchema)   create_tags(spec)   load_docstrings(spec, app)   return spec


image alt


Swagger UI


Swagger UI позволяет создать интерактивную страницу с документацией.

Создадим эндпоинт, который будет возвращать спецификацию в json формате, и вызываем в нем get_apispec:

@app.route('/swagger')def create_swagger_spec():   return json.dumps(get_apispec(app).to_dict())

Теперь, когда мы получили json спецификацию, нам нужно сформировать из неё html документ. Для этого воспользуемся пакетом flask_swagger_ui, с помощью которого можно встроить интерактивную страницу с документацией на базе Swagger UI в наше Flask приложение:

from flask_swagger_ui import get_swaggerui_blueprintSWAGGER_URL = '/docs'API_URL = '/swagger'swagger_ui_blueprint = get_swaggerui_blueprint(   SWAGGER_URL,   API_URL,   config={       'app_name': 'My App'   })


Таким образом, мы сделали эндпоинт /docs, при обращении по которому получаем документацию следующего вида:

image alt


image alt

GitLab Pages + ReDoc



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

Использование GitLab позволяет сгенерировать такую статическую страницу с документацией в CI/CD процессах.

Таким образом, мы один раз соберем документацию, и при обращении по заданному адресу она будет просто отображаться без какой-либо дополнительной обработки.

Для этого сохраним APISpec в YAML файл:

DOCS_FILENAME = 'docs.yaml'def write_yaml_file(spec: APISpec):   """ Экспортируем объект APISpec в YAML файл.   :param spec: объект APISpec   """   with open(DOCS_FILENAME, 'w') as file:       file.write(spec.to_yaml())   print(f'Сохранили документацию в {DOCS_FILENAME})

Теперь, когда мы получили YAML файл по спецификации OpenAPI, нужно сформировать HTML документ. Для этого будем использовать ReDoc, так как он позволяет сгенерировать документ в gitlab-ci с красивой и удобной структурой. Публиковать его будем с помощью GitLab Pages.

Добавим следующие строки в файл gitlab-ci.yml:

pages: stage: docs image: alpine:latest script:   - apk add --update nodejs npm   - npm install -g redoc-cli   - redoc-cli bundle -o public/index.html docs.yaml artifacts:   paths:     - public

Стоит отметить, что index.html нужно сохранять в папку public, так как она зарезервирована GitLabом.

Теперь, если мы запушим изменения в репозиторий, по адресу namespace.gitlab.com/project появится документация:

image alt


Также путь до документации можно посмотреть в Settings/Pages

Пример документации с использованием ReDoc: ivi-ru.github.io/hydra

Заключение


Таким образом, мы научились собирать OpenAPI документацию с использованием ReDoc и хостить ее на GitLab Pages. В эту статью не попало еще несколько возможностей этих инструментов, например, валидация параметров с помощью marshmallow. Но основной ее целью было показать непосредственно процесс создания документации.

Полезные ссылки


Подробнее..

Создание современного API на PHP в 2020 году

01.10.2020 22:19:19 | Автор: admin
Итак, на примере этого API, я хочу показать современную PHP архитектуру для высоко нагруженных проектов. Когда проект еще в самом начале, и не то, что бизнеслогика (взаимоотношения с базой данных) не прописана, но и сама бизнесмодель не очень ясна, построение эффективной IT архитектуры может идти только одним путем: необходимо жестко разделить frontend и backend.

Что обычно делали в таких ситуациях два-три года назад? Брался монолитный фрейворк типа Laravel или Yii2, вся бизнес модель разбивалась, худо-бедно, на блоки, а эти блоки уже имплементировались как модули фреймворка. В итоге еще через 3 года получалась огромная не поворотная машина, которая сама по себе медленная, а становилась почти невыносимо медленной, в которой фронтенд рендится через бэкенд по средством классической MVC архитеркутуры (пользователь отправил запрос, контроллер его подхватил, вызвал модель, та в свою очередь чего-то там натворила с базой данных, вернула все контроллеру, а том наконец-то вызвал вьювер, вставил туда данные из модели и отдал это все пользователю, который уже успел открыть очередную банку пива...). А ну еще особо продвинутые ребята они не просто вьюверели Tweeter Bootstrap, а во вьювер вкручивали самом деле очень хорошие библиотеки типа JQuery или вместо вьювера использовали какой-нибудь фронтенд фреймворк. В итоге поддерживать такой БеЛаЗ становилось все сложнее, а ввести нового программиста в команду было очень сложно ибо не все рождаются Энштейнами. Добавим сюда тотальное отсутствие документации разработчика (камменты в 9000 файлах почитаешь там все есть!) и в итоге, смотря на это все, становилось по-настоящему грустно

Но тут произошли ряд событий которые в корне изменили ситуацию. Во-первых, наконец-то, вышли стандарты PSR и Symfony внезапно перестал быть единственным модульным фремворком, во-вторых вышел ReactJS, который позволил полноценно разделить фронтенд от бэкенда и заставить их общаться через API. И добивая последний гвоздь в крышку гроба старой системы разработки (MVC это наше все!) выходит OpenAPI 3.0, собственно который и регулирует стандарты этого общения через API между фронтендом и бэкендом.

И в мире PHP стало возможно делать следущее:

1. Разделить бизнес модель на сервисы и микросервисы, и не поднимать для этого весь БеЛаЗ, а обслуживать запросы микросервисов буквально в пару строк кода самый банальный пример: похожие товары (отдельный запрос GET отдельный, малюпасенький API, который его обработал и вернул, и мгновенный вывод этой информации ReactJS в браузере пользователя. Основной БеЛаЗ даже и не узнал о том, что произошло

2. Писать API стандартизировано по стандарту OpenAPI 3.0 ( swagger.io )в виде YAML или JSON файла, когда каждый программист не лезет в грязных сапогах в ядро системы, а например, культурно дописывает свою часть в общем YAML файле. тем самым устраняя вероятность ошибки от человека к человеку и уменьшая количество седых волос у тестеровщиков. Просто потом из готового YAML сгенерировал полностью рабочий и даже с миделваре сервер. На каком угодно языке и фреймфорке.

3. Теперь не надо стало нанимать кого-то, чтобы он, этот кто-то писал для вашего API библиотеки, которыми ваши клиенты будут обращаться к вашему API: github.com/OpenAPITools/openapi-generator я насчитал генерацию более 40 серверов для API и даже не стал считать библиотеки для доступа к ним, ибо единственный язык программирования который я там не нашел Dlang)

Итак с API думаю разобрались. Пишем YAML файл в сваггере или инсомнии, через OpenAPITools генерируем сервер и пользовательские библиотеки. Абстрактные классы не трогаем (папочка lib) а всю бизнеслогику выносим в наследуемые классы (папочка scr), для того чтобы при последующей перегенерации сервера мы ничего не сломали, а просто тупо скопировали папочку lib к себе в корень фреймфорка и добавили новую бизнес логику в не перемещаемой папочке scr. API готов Быстро, просто, функционально. Клиентский библиотеки тоже готовы. Директор даже не успел вернуться с Мальдивов

Теперь становится вопросы, точнее два вопроса, а что у нас перед API и соответственно, что у нас под хвостом после API.

Ответы:
1.Там вдали за рекой, далеко перед API у нас цветет, расползается на новую функциональность и картинки ФРОНТЕНД (Предпочтительнее ReactJS, но Vue тоже сойдет. Хотя там из коробки всего столько много, что утяжелять процесс он будет, а вот насколько в реальной жизни это понадобится не совсем понятно и зависит напрямую от бизнес модели). И НЕТ! Я к этому зверю даже близко подходить не буду, ибо с детства у меня на него аллергия. Тут нужен отдельный специалист. Я не фулстак и не пишу фронтенд.

2. Прямо вот перед самим API у нас НЕ УГАДАЛИ не NGINX, а RoadRunner roadrunner.dev/features. Заходим, читаем, понимаем, что это быстрее и вокеры расписываются по количеству процессоров, а посему никогда не будет таблички М НА ПРОФИЛАКТИКЕ, ибо просто надо вокеры переключить.

И на этом моменте хочу остановиться подробнее. Ибо в моем понимании есть три пути как мух ловить, запросы то бишь

1. В случае если весь API написан уже, и будет написан в дальнейшем, на PHP голову ломать не зачем, ставим RoadRunner с prometheus.io
2. В случае если система собирается из разных кусков, разные сервисы написаны на разных языках и дальше тоже не понятно на чем их писать будут:
2.1. Ставим NGINX UNIT пользуемся поддерживаемыми языками.
2.2. Поднимаем ВСЕ РАВНО КАКУЮ систему контейнеров, Docker, LXC, LXD. Выбор опять же зависит от размера проекта поддерживать сборку PROXMOX-LXC на хостинге в 12 процессоров, c 32Гб памяти, за 40 евро в месяц будет в разы дешевле, чем Docker сборки на Google Cloud Platform. В каждый контейнер ставим подходящий к языку сервер, и связываем все это HAProxy www.haproxy.org. HAProxy шикарный балансер и прокси сервер который в корпоративной среде, не менее популярен чем NGINX. Что он делает, а чего нет читаем тут cbonte.github.io/haproxy-dconv/2.3/intro.html пункт 3.1. При такой архитектуре сервисы или микросервисы могут писаться на чем угодно и никто не зависит от ограничений накладываемыми RoadRunner или NGINX UNIT.

3. Под хвостом Cycle ORM. Не ленимся смотрим видио
www.youtube.com/watch?v=o1wzzSoJJHg&ab_channel=fwdays, что будет стоять за ней конкретно MySQL или Postgres опять, я бы оставил на после того, как будет понятна бизнес схема проекта. MySQL проще масштабируется, в Postgres больше бизнес логики перенесенной внутрь самой базы.

4. Пример который можно посмотреть и пощупать
bitbucket.org/rumatakira/api-example/src/master. Там за основу взято тестовое задание. Все самые полезные вещи находятся в папке EXTRAS. Там уже сть jar file генератора, YAML swagger файл API, сгенерированный API stub через OpenAPITools в SLIM4. Даже с аутентификацией и мидлваре. документация на API сгенрированная, правда swagger, не OpenAPITools. Предполагается, что некоторые юзеры залогинены и им выдан токен. Там уже стоит RoadRunner впереди. Стек PHP 7.4.10, PostgreSQL 12.4.

После Git clone, composer install в файле /bootstrap.php прописываем юзера и пароль к базе, которую вначале создаем, потому что это PostgreSQL, по умолчанию сервер слушает локальный порт 8888, если нужно меняем в файле /.rr.yaml, и
выполняем команду: composer run-script fill-database. Все никаких миграций )). Пользуемся.

P.S. Всем залогиненым пользователям присвоен одинаковый токен. Вообще в примере нет никаких валидаций ввода пользователя и почти нет защит это пример в основном нацеленный на архитектуру.

С уважением,
Кирилл Лапчинский
api-studio.com
mail@api-studio.com
Подробнее..

Четыре API для базы данных

10.02.2021 20:14:57 | Автор: admin

Одновременный сеанс в IRIS: SQL, объекты, REST, GraphQL

Казимир Малевич, Спортсмены (1932)Казимир Малевич, Спортсмены (1932)

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

Казимир Малевич (1916)

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

Есть несколько соображений о том, почему стоит поговорить про обмен данными через API на основе SQL/REST/GraphQL, в противовес представлению их в виде типов/объектов:

  • это массово изучаемые и достаточно легко используемые технологии;

  • их широкая популярность в доступных и открытых программных продуктах просто невероятна;

  • часто, особенно в вебе и в базах данных, у вас просто нет выбора;

  • или наоборот, когда у вас всё ж таки есть выбор его надо сделать осознанно:

  • и главное, внутри в границах этих API остаются объекты, как наиболее адекватный способ их реализации в коде.

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

Сегодня, данные хранятся, как давно повелось, на жёстких дисках в HDD или, уже по современному, в микросхемах флеш-памяти в SSD. Данные пишутся и читаются потоком, состоящем из отдельных блоков хранения на HDD/SSD.

Деление на блоки не случайное, а задаваемое физикой/механикой/электроникой накопителя данных. В HDD это дорожки/сектора на вращающемся магнитном диске. В SSD это сегменты памяти в перезаписываемом кремниевом чипе. Суть одна это блоки информации, из частей которых необходимо найти и собрать воедино нужные нам кусочки данных. Собрать в структуры, которые соответствуют нашей модели/типу данных со значением необходимым на момент запроса. За этот процесс как раз отвечает связка из СУБД и файловой подсистемы в операционной системе.

По правде говоря, можно обращаться и минуя СУБД напрямую к операционной системе или даже напрямую к HDD/SSD. Но тогда мы теряем два супер важных мостика к данным первый, между блоками хранения и файловыми потоками, и, второй, между файлами и упорядоченной структурой в модели базы данных. Или, говоря другими словами, мы берём на себя ответственность по разработке всего этого объёма кода для обработки блоков/файлов/моделей со всеми оптимизациями, тщательной отладкой и долговременными испытаниями на надёжность.

Так вот, СУБД дают нам прекрасную возможность обращаться с данными на языке высокого уровня сразу оперируя понятными моделями и представлениями. Хвала им за это. А хорошие СУБД и платформы данных, такие как InterSystems IRIS, предоставляют больше доступ к упорядоченным данным сразу множеством способов одновременно. И выбор уже за программистом, какой из них выбрать для своего проекта.

Так воспользуемся же множественными привилегиями, которые даёт нам IRIS. Сделаем код красивее и полезнее сразу будем применять объектно-ориентированные язык ObjectScript для использования и разработки API. То есть, например, SQL код будем вызывать прямо изнутри программы на ObjectScript. А для других API воспользуемся готовыми библиотеками и встроенными средствами ObjectScript.

Для примеров будем использовать данные из замечательного интернет-проекта "SQL Zoo", в котором обучают языку запросов SQL. Эти же данные будем использовать в других примерах API.

Если хочется сразу посмотреть на многообразие подходов к проектированию API и воспользоваться готовыми решениями, то вот интересная и полезная коллекция публичных API, которая коллективно собирается в проекте на гитхабе https://github.com/public-apis/public-apis

SQL

Начинаем вполне естественно с SQL. Кто ж его не знает?

Для изучения SQL есть гигантское количество учебных курсов и книг. Мы будем опираться на https://sqlzoo.net. Это хороший начальный онлайн курс по SQL с примерами, прохождением заданий и справочником языка.

Будем переносить задачки из SQLZoo на платформу IRIS и получать аналогичные решения разными способами.

Как быстро получить доступ к InterSystems IRIS на своём компьютере? Один из самых быстрых вариантов развернуть контейнер в докере из готового образа InterSystems IRIS Community Edition бесплатная версия InterSystems IRIS Data Platform

Другие способы получить доступ к InterSystems IRIS Community Edition на портале обучения.

Перенесём данные с SQLZoo в хранилище нашего собственного экземпляра IRIS. Для этого:

  1. Открываем портал управления (у меня, например, по ссылке http://localhost:52773/csp/sys/UtilHome.csp),

  2. Переключаемся на область USER - в Namespace %SYSнажимаем ссылку Switch и выбираем USER

  3. Переходим в меню Система > SQL - открываем Обозреватель системы, затем SQL и жмём кнопку Запустить.

  4. Справа на будет открыта закладка "Исполнить запрос" с кнопкой "Исполнить" - она то нам и нужна.

Более подробно о работе с SQL через портал управления можно посмотреть в документации.

Кстати, другим, удобным вам, способом попробовать SQL доступ к базе данных в IRIS, может оказаться популярный у разработчиков редактор Visual Studio Code с плагином SQLTools и драйвером "SQLTools Driver for InterSystems IRIS". Попробуйте этот вариант.

Инструкция как настроить доступ к IRIS и разработке на ObjectScript в VSCode.

Готовые скрипты для развёртывания базы данных и набора тестовых данных SQLZoo есть в описании на сайте в разделе Данные.

Пара прямых ссылок для таблицы World:

Скрипт для создания базы данных можно выполнить тут же на портале управления IRIS в форме "Исполнитель запросов".

CREATE TABLE world(

name VARCHAR(50) NOT NULL

,continent VARCHAR(60)

,area DECIMAL(10)

,population DECIMAL(11)

,gdp DECIMAL(14)

,capital VARCHAR(60)

,tld VARCHAR(5)

,flag VARCHAR(255)

,PRIMARY KEY (name)

)

Для загрузки набора тестовых над формой "Исполнитель запросов" есть меню Мастера > Импорт данных. Для этого каталог с файлом тестовых данных необходимо добавить заранее, при создании вашего контейнера, или загрузить с вашего компьютера через браузер такая опция есть тут же в мастере импорта данных панели управления.

Проверяем наличие таблицы с данными запуском простейшего скрипта в форме "Исполнитель запросов":

SELECT * FROM world

Теперь нам доступны примеры и задания с сайта "SQL Zoo". Все примеры ниже реализуют SQL запрос из первого задания:

SELECT population

FROM world

WHERE name = 'France'

Так что можете смело продолжать начатое нами исследование API перенося задания из SQLZoo на платформу IRIS.

Внимание! Как я обнаружил, данные в интерфейсе сайта SQLZoo и данные его экспорта отличаются. Как минимум в первом примере расходятся значения по населению Франции и Германии. Не переживайте. Вот данные Евростата для ориентировки.

Что бы плавно перейти к следующему шагу объектный доступ к нашей базе данных, сделаем небольшой промежуточный шаг от "чистых" SQL запросов к SQL запросам встроенные в код приложения на ObjectScript объектно-ориентированном языке, встроенном в IRIS.

Class User.worldquery

{

ClassMethod WhereName(name As %String)

{

&sql(

SELECT population INTO :population

FROM world

WHERE name = :name

)

IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE," ",%msg QUIT}

ELSEIF SQLCODE=100 {WRITE "Query returns no results" QUIT}

WRITE name, " ", population

}

}

Проверяем результат в терминале:

do ##class(User.worldquery).WhereName("France")

В ответ должны вернуться название страны и число жителей.

Объекты/типы

Это общая преамбула к истории про REST/GraphQL. Мы реализуем API в сторону веб протоколов. Чаще всего у нас под капотом на серверной стороне будет исходный код на каком-то из языков, хорошо поддерживающих типизацию или даже целиком объектно-ориентированную парадигму. Вот те самые: Spring на Java/Kotlin, Django на Python, Rails на Ruby, ASP.NET на C# или даже Angular на TypeScript. И безусловно объекты в ObjectScript, родном для платформы IRIS.

Почему это важно? Типы и объекты в вашем коде, при отправке вовне, будут упрощены до структур данных. Необходимо учитывать упрощение моделей в программе, что аналогично учёту потерь в реляционных моделях. И заботиться о том, что на другой стороне API, модели будут адекватно восстановлены и использованы вами или другими разработчиками без искажений. Это дополнительная нагрузка и увеличение ответственности программиста. Вне кода, вне помощи трансляторов/компиляторов и других автоматических инструментов при создании программ, необходимо вновь и вновь заботиться о корректной передаче моделей.

Если посмотреть на вопрос с другой стороны, то пока не видно на горизонте технологий и инструментов легко передающих типы/объекты из программы на одном языке в программу на другом. Что остаётся? Вот эти упрощения SQL/REST/GraphQL и разливанное море документации с описанием API на человечески понятном языке. Неформальная, с компьютерной точки зрения, документация для разработчиков это как раз то, что следует всячески переводить в формальный код, который компьютер обрабатывать умеет.

Подходы к решению этих проблем предпринимаются регулярно. Одно из успешных это кросс языковая парадигма в объектной СУБД платформы IRIS.

Что бы чётко понимать как соотносятся модели ОПП и SQL в IRIS, предлагаю посмотреть на таблицу:

Объектно-ориентированное программирование (ООП)

Структурированный язык запросов (SQL)

Пакет

Схема

Класс

Таблица

Свойство

Столбец

Метод

Хранимая процедура

Отношение между двумя классами

Ограничение внешнего ключа, встроенный join

Объект (в памяти или на диске)

Строка (на диске)

Более подробно про отображение объектной и реляционной модели можно посмотреть в документации IRIS.

При выполнении нашего SQL запроса на создание таблицы world из примера выше, IRIS автоматически генерирует у себя описания соответствующего объекта класс с именем User.world.

Class User.world Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = world ]

{

Property name As %Library.String(MAXLEN = 50) [ Required, SqlColumnNumber = 2 ];

Property continent As %Library.String(MAXLEN = 60) [ SqlColumnNumber = 3 ];

Property area As %Library.Numeric(MAXVAL = 9999999999, MINVAL = -9999999999, SCALE = 0) [ SqlColumnNumber = 4 ];

Property population As %Library.Numeric(MAXVAL = 99999999999, MINVAL = -99999999999, SCALE = 0) [ SqlColumnNumber = 5 ];

Property gdp As %Library.Numeric(MAXVAL = 99999999999999, MINVAL = -99999999999999, SCALE = 0) [ SqlColumnNumber = 6 ];

Property capital As %Library.String(MAXLEN = 60) [ SqlColumnNumber = 7 ];

Property tld As %Library.String(MAXLEN = 5) [ SqlColumnNumber = 8 ];

Property flag As %Library.String(MAXLEN = 255) [ SqlColumnNumber = 9 ];

Parameter USEEXTENTSET = 1;

/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement. Do not edit the SqlName of this index.

Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];

/// DDL Primary Key Specification

Index WORLDPKey2 On name [ PrimaryKey, Type = index, Unique ];

}

Это отличная заготовка для разработки своего приложения в объектно-ориентированном стиле. Достаточно добавить методы в класс на ObjectScript, имеющий готовые привязки к базе данных. Фактически, методы этого класса являются хранимыми процедурами, если использовать терминологию SQL-подхода.

Пробуем реализовать тот же пример, что выше делали на SQL, Добавляем в класс User.world метод WhereName, который играет роль конструктора объекта "информация о стране"по заданному названию страны:

ClassMethod WhereName(name As %String) As User.world

{

Set id = 1

While ( ..%ExistsId(id) ) {

Set countryInfo = ..%OpenId(id)

if ( countryInfo.name = name ) { Return countryInfo }

Set id = id + 1

}

Return countryInfo = ""

}

Проверяем в терминале:

set countryInfo = ##class(User.world).WhereName("France")

write countryInfo.name

write countryInfo.population

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

Например, для нашего класса, зная сгенерированное IRIS имя индекса по названию страны WORLDPKey2 можно инициализировать/конструировать объект из базы данных одним скоростным запросом:

set countryInfo = ##class(User.world).WORLDPKey2Open("France")

Проверяем так же:

write countryInfo.name

write countryInfo.population

Хорошие рекомендации по выбору между объектным и SQL доступом к хранимым объектам есть в документации. Хотя, в любом случае, вы вольны для своих задач использовать только один из них на 100%.

Плюс ещё и том, что благодаря наличию в IRIS готовых бинарных связок с распространёнными ООП языками, какими как Java, Python, С, C# (.Net), JavaScript и, даже быстро набирающему популярность, Julia [1][2]. Всегда есть возможность выбора удобных вам языковых средств разработки.

Теперь приступим непосредственно к разговору о данных в веб-API.

REST он же RESTful веб-API

Вырываемся за границы сервера и уютного терминала. Пробираемся поближе к массовым интерфейсам браузерам и им подобным приложениям. Туда, где в основе взаимодействия систем используются гипертекстовые протоколы семейства http. В IRIS "из коробки" для этого работает связка из собственно сервера баз данных и http-сервера Apache.

Representational state transfer (REST) архитектурный стиль проектирования распределённых приложений и, в частности, веб-приложений. Ему, между прочим, пошел уже третий десяток лет. REST применяется повсеместно не имея какой-либо стандартизации, кроме как опоры на протоколы http/https. Совсем не то, что его коллеги/конкуренты в веб-службах на базе стандартов для протоколов SOAP и XML-RPC.

Глобальным ID в REST является URL и он определяет каждую очередную информационную единицу при обмене с базой данных или с бекэнд приложением.

Документация по разработке REST-сервисов в IRIS.

В нашем примере основой идентификатором будет что-то вроде основы из адреса сервера IRIS http://localhost:52773 и приставленного к нему пути до наших данных /world/ или более конкретно по стране /world/France.

Примерно так в докер-контейнере:

http://localhost:52773/world/France

При разработке полноценного приложения в документации IRIS рекомендуется воспользоваться одним из трёх генераторов кода. Например, один из них основан на описании REST API по спецификации OpenAPI 2.0.

Мы пойдём простым путём реализуем API вручную. В нашем примере сделаем простейшее REST-решение, которое требует всего два шага в IRIS:

  1. Создать класс-диспетчер путей в URL, который будет унаследован от системного класса %CSP.REST

  2. Добавить обращение к нашему классу-диспетчеру при настройке веб-приложения IRIS

Шаг 1 Класс-диспетчер

Реализация класса должна быть достаточно понятна. Делаем по инструкции в документации для "ручного" REST: https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_csprest#GREST_csprest_urlmap

/// Description

Class User.worldrest Extends %CSP.REST

{

Parameter UseSession As Integer = 1;

Parameter CHARSET = "utf-8";

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]

{

<Routes>

<Route Url="/:name" Method="GET" Call="countryInfo" />

</Routes>

}

}

И внутри добавляем метод-обработчик ровно так же, как были устроены вызовы в терминале из предыдущего примера:

ClassMethod countryInfo(name As %String) As %Status

{

set countryInfo = ##class(User.world).WhereName(name)

write "Country: ", countryInfo.name

write "<br>"

write "Population: ", countryInfo.population

return $$$OK

}


Как можете видеть, для передачи из входящего REST-запроса в параметра name вызываемого метода-обработчика в диспетчере указывается параметр с двоеточием в начале ":name".

Шаг 2 Настройка веб-приложения IRIS

В меню System Administration > Security > Applications > Web Applications

добавляем новое веб-приложение с указанием точки входа по URL на /world и обработчик наш класс-диспетчер worldrest.

Лайфхаки

Веб-приложение после настройки должно сразу отзываться по ссылке http://localhost:52773/world/France (регистр букв для передачи данных запроса в параметр метода важен, должно быть как в базе данных).

При необходимости используйте инструменты отладки хорошее описание есть в этой статье из двух частей (в комментариях загляните тоже). https://community.intersystems.com/post/debugging-web

Если появляется ошибка "401 Unauthorized", а вы уверены, что класс-диспетчер есть на сервере и ссылка правильная, то попробуйте добавить в настройках веб-приложения роль %All во вкладке "Application Roles". Это не совсем безопасно и надо понимать что вы разрешаете, но для локальной работы допустимо.

GraphQL

Эта территория новая, в том смысле, что в документации IRIS об API с использованием GraphQL вы ничего не найдёте сегодня. Но это не помешает нам воспользоваться таким замечательным инструментом.

Всего пять лет как GraphQL появился на публике.

Это язык запросов для API. И, наверное, это лучшая технология, которая возникла при совершенствовании REST-архитектуры и различных веб-API. Развитием GraphQL занимается Linux фонд. Небольшая вводная статья для начинающих.

А благодаря стараниям энтузиастов и инженеров InterSystems воспользоваться возможностями GraphQL в IRIS можно с 2018 года.

Статья на Хабре: Как я реализовал GraphQL для платформ компании InterSystems

En: GraphQL понимаем, объясняем, внедряем

Приложение для GraphQL состоит из двух модулей бекенд приложения на стороне IRIS и фронтэнд части, работающей в браузере. То есть, необходимо настроить по инструкции для веб-приложения GraphQL и GraphiQL.

Для примера, как это выглядит у меня на IRIS в докер-контейнере. Настройки веб-приложения GraphQL выполняющее функции REST-диспетчера и обработчика схемы базы данных:

И второе приложение GraphiQL пользовательских интерфейс для браузера, написан на HTML и JavaScript:

Запускает по адресу http://localhost:52773/graphiql/index.html

Без дополнительных ограничительных настроек, приложение сразу подхватывает все схемы баз данных, доступных в области установки. Это значит, что наши примеры работать начинают сразу. Плюс во фронтэнде есть замечательная организация подсказок из доступных объектов.

Пример GraphQL запроса к нашей базе данных:

{

User_world ( name: France ) {

name

population

}

}


И соотвествующий ему ответ:

{

"data": {

"User_world": [

{

"name": "France",

"population": 65906000

}

]

}

}

Так это выглядит в браузере:

Итоги


Возраст технологии

Пример запроса

SQL

50 лет

Кодд

SELECT population

FROM world

WHERE name = 'France'

ООП

40 лет

Кэй, Ингаллс

set countryInfo = ##class(User.world).WhereName("France")

REST

20 лет

Филдинг

http://localhost:52773/world/France

GraphQL

5 лет

Байрон

{ User_world ( name: France ) {

name

population

}

}

  1. Жизненной силы и энергии у технологий SQL, REST и, теперь наверное, GraphQL много и это надолго. Они все прекрасно уживаются внутри платформы IRIS и, при должном внимании разработчика, дарят радость в создании программ работы с данными.

  2. Хоть это не упомянуто в статье, другие великолепные API на основе XML (SOAP) и JSON в IRIS также реализованы на должном уровне. Пользуйтесь на здоровье.

  3. Помните, что любой обмен данными через API это всё же неполноценная, а урезанная версия передачи объектов. И задача корректной передачи информации о типа данных в объекте, потерянной в API остаётся, на разработчике, а не в коде.


Вопрос к вам, дорогие читатели

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

И поэтому, очень интересно ваше мнение, помогает ли такой подход для "легкого старта", какие шаги процесса доставляют сложность для начинающих осваивать инструменты работы с API в IRIS? Что показалось "неочевидным препятствием"? Поспрашивайте у осваивающих IRIS и напишите мне в комментариях. Думаю, это всем будет полезно обсудить.

Подробнее..

Что было раньше код или документация? OpenApi (OAS 3.0) и проблемы кодогенерации на Java

12.11.2020 14:14:33 | Автор: admin

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

Эту проблему отчасти удалось решить при помощи спецификации OpenAPI(OAS3.0)[1], но все равно часто встает вопрос о правильном применении и подводных камнях кодогенерации, например на языке Java. И можно ли полностью предоставить аналитикам написание функциональных требований, документации и моделей в ymlформе для OpenAPI, а разработчикам предоставить возможность только написание бизнес-логики?

Что было раньше: код или документация?

Я уже около десяти лет занимаюсь разработкой на Java,и почти в каждом проекте так или иначе приходилось сталкиваться с необходимостью поделиться разработанным сервисом с другими людьми. Иногда достаточно просто составить понятное описание APIсуществующего сервиса, но в серьезной enterprise-разработке такое описание часто создается еще на этапе аналитики в виде полноценного документа и является основой для грядущей работы.

В некоторых случаях такие документы составляются на этапе проектирования архитектуры в обычном текстовом формате, а потом передаются разработчику для реализации. Однако, для разработки REST-APIтакой подход не очень удобен и усложняет жизнь как программистам, которые должны еще правильно понять написанное, так и командам, которые будут с получившимся продуктом интегрироваться.К тому же документы могут интерпретироваться людьми по-разному, что обязательно приведет к необходимости дополнительных обсуждений, согласований и переделок, что в конечном счете срывает сроки и добавляет дефекты в продукт. Хотя, метод не очень удобен, иногда он до сих пор применяется при проектировании проектов без OpenAPI(например файловая интеграция, интеграция через БД, интеграция через очереди и общую шину).

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

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

В случае крупных и серьезных проектов, ручное документирование приводит к ряду больших проблем. В SOAPсервисах потребители, получая ссылку на WSDL-документ, генерируют на своей стороне модель данных и клиентский код. В отличие от SOAPпри интеграции OAS3.0 или OpenAPISpecificationявляется не просто спецификацией, а целой экосистемой инструментов для создания RESTсервисов, и она позволяет решить как задачу генерации документации по существующему коду, так и обратную - генерацию кода по документации. Нопрежде чем ответить на вечный вопрос, что первично, нужно определиться с целями и потребностями проекта.

Генерация документации по коду

Первым рассмотрим подход с автодокументируемым кодом[2,3]. Простота здесь заключается в том, что при использовании SpringMVCот разработчиков требуется всего лишь подключить пару библиотек, и на выходе уже получается вполне сносная документация с возможностью интерактивного взаимодействия с API.

Зависимости:

Spoiler

Maven

<dependency>   <groupId>org.springdoc</groupId>   <artifactId>springdoc-openapi-ui</artifactId>   <version>1.4.8</version></dependency>
<dependency>   <groupId>org.springdoc</groupId>   <artifactId>springdoc-openapi-webflux-ui</artifactId>   <version>1.4.8</version></dependency>

Gradle

compile group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.4.8'
compile group: 'org.springdoc', name: 'springdoc-openapi-webflux-ui', version: '1.4.8'

Результат доступен на: http://localhost:8080/swagger-ui.html

Сгенерированная документация по ссылке: http://localhost:8080/v3/api-docs

Плюсы подхода:

  • Возможность быстро сгенерировать документацию по существующему коду

  • Простота интеграции

  • Актуальная онлайн документация

  • Встроенная возможность интерактивного взаимодействия

Минусы подхода:

  • Документация написанная для разработчиков, содержит немного бизнес информации

  • При требовании подробного описания моделей и API возникает необходимость внедрять в код аннотации.

  • Для любых изменений требуется участие разработчика и правки кода

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

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

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

Генерация кода по документации

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

Плюсы подхода

  • Ведение документации становится доступно не только разработчикам, но и техническим и системным аналитикам

  • Есть возможность разделить код и документацию

  • Можно объединить описание моделей, API и функциональных/бизнес требований

  • Любые изменения, инициированные бизнесом, сразу же применяются к модели и контроллерам

  • В случае критического несоответствия сервиса документации, сервис не соберется уже на этапе компиляции и потребует привести интерфейсы в соответствие с реализацией

  • Документацию в yaml формате легко предоставить потребителям сервиса при помощи сторонних библиотек, например redoc[4]

  • На основе yaml файлов можно описать сервисы на стороне потребителя, например, OpenApi для получения нотификаций от основного сервиса

  • На основе этой документации генерировать у себя тестовые сервисы для интеграционных и функциональных тестов

Минусы подхода

  • Более актуально для крупных и долгоживущих проектов (от одного года разработки)

  • Требует знания yaml формата для описания моделей

  • Требует навыки работы с markdown для описания функциональных требований

  • Нужно хорошее взаимодействие в команде, чтобы результаты изменения документации и бизнес-логики в сервисе были синхронизированы

Подводные камни и опыт реализации:

Задача по применению второго подхода впервые появилась, когда потребовалось предоставить потребителям актуальную веб-версию документации с подробным описанием функциональных требований и бизнес-кейсов. Написание такой документации и ее актуализацию предстояло вести техническим аналитикам без возможности напрямую редактировать код. И еще одним важным требованием было красивое визуальное представление документации. Далее опишу базовый набор конфигураций для достижения этой цели и возможные подводные камни и проблемы[2,3]

Первым делом добавляем зависимость на mavenплагин

Spoiler
<dependency>   <groupId>io.swagger.codegen.v3</groupId>   <artifactId>swagger-codegen-maven-plugin</artifactId>   <version>3.0.21</version></dependency>
   <artifactId>springdoc-openapi-webflux-ui</artifactId>
   <version>1.4.8</version>

И конфигурируем генератор кода

Spoiler
<plugin>   <groupId>io.swagger.codegen.v3</groupId>   <artifactId>swagger-codegen-maven-plugin</artifactId>   <version>3.0.21</version>   <executions>       <execution>           <id>1</id>           <goals>               <goal>generate</goal>           </goals>           <configuration>               <groupId>com.habr</groupId>               <artifactId>oas3</artifactId>               <inputSpec>${project.basedir}/src/main/resources/habr-1.yaml</inputSpec>               <output>${project.build.directory}/generated-sources/</output>               <language>spring</language>               <configOptions>                   <sourceFolder>src/gen/java/main</sourceFolder>                   <library>spring-mvc</library>                   <interfaceOnly>true</interfaceOnly>                   <useBeanValidation>true</useBeanValidation>                   <dateLibrary>java8</dateLibrary>                   <java8>true</java8>                   <apiTests>false</apiTests>                   <modelTests>false</modelTests>               </configOptions>               <modelPackage>com.habr.oas3.model</modelPackage>               <apiPackage>com.habr.oas3.controller</apiPackage>           </configuration>       </execution>       <execution>...</execution>     ...   </executions>

В случае наличия в проекте нескольких файлов документации следует указывать их в разных executionблоках, однако, каждый запуск перетирает результаты предыдущего запуска (либо игнорирует при включении определенного флага). При наличии нескольких yamlфайлов рекомендуется избегать коллизий в моделях и интерфейсах чтобы запуски не перетирали уже сгенерированные модели.

InputSpecсодержит путь к документации, а modelPackageи apiPackageпакеты для сгенерированных моделей. Также можно генерировать моковые реализации интерфейсов для тестов (при генерации некоторый контроллер, что отвечает типичным ответом).

Флаг interfaceOnlyпозволяет генерировать только интерфейсы контроллеров и предоставить разработчику лишь контракты для реализации.

Общая структура yaml документации:

  1. openApi - Содержит версию

  2. info - метаданные для api

  3. servers - информация о серверах c api

  4. tags - дополнительные метаданные, в этом блоке нужно писать текст ФТ и регламента взаимодействия

  5. paths - описание endpoints и интерфейсов контроллеров

  6. components - описание модели данных

  7. security - схема безопасности

Исходный код на github и пример документации можно посмотреть здесь

Визуализация и предоставление потребителю: swagger-ui vs redoc

Теперь поговорим про способы поставки полученной документации. Помимо ручного написания и передачи любым удобным каналом бытует подход с онлайн-документацией в связке с существующим сервисом. Рассмотрим самые удобные и популярный способы такой поставки.

Swagger-ui

Удобная реализация с возможностью интерактивного взаимодействия. Идет из коробки в проекте swagger.io[5]. Можно подключить как библиотеку вместе с сервисом или развернуть статикой отдельно.

Пример визуализации документации тестового сервиса:

Redoc

Он позволяет отобразить и кастомизировать документацию альтернативным способом. Более удобная и красивая структура документации[4].

Пример визуализации документации тестового сервиса:

Хотя, redocреализован на react, его также можно использовать и как VueJSкомпонент:

Spoiler
<template>    <div v-if="hasYmlContent">        <RedocStandalone :spec='ymlObject' :options='ropt' />    </div></template><style...><script>    import AuthService from '../services/auth.service';    import DocumentationService from '../services/documentation.service'    import {RedocStandalone} from 'redoc'    import {API_URL} from "../services/auth-header";    import YAML from "yamljs"    import Jquery from "jquery"    export default {        name: 'documentation',        components: {'RedocStandalone': RedocStandalone},        props: ['yml'],        data: function() {            return {                ymlContent: false,                ropt:{...},            }        },        created() {...},        computed: {...}    };</script>

Выводы

Проведение аналитики и проектирование системы до начала этапа разработки помогает лучше понять задачи и избежать многих ошибок при реализации. Проект OAS3.0 показал себя очень удобным для описания документации и поставки ее потребителям сервиса.

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

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

Источники

  1. https://swagger.io/specification/

  2. https://www.baeldung.com/spring-rest-openapi-documentation

  3. https://github.com/springdoc/springdoc-openapi

  4. https://redocly.github.io/redoc/

  5. https://swagger.io/tools/swagger-ui/

Подробнее..

Swagger (OpenAPI 3.0)

09.02.2021 14:04:30 | Автор: admin

Всем привет!!! Это мой первый пост на Хабре и я хочу поделиться с вами своим опытом в исследование нового для себя фреймворка.

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

Сейчас хочу поделиться ею в надежде, что кому-то она поможет в изучение Swagger (OpenApi 3.0)

Введение

Я на 99% уверен у многих из вас были проблемы с поиском документации для нужного вам контроллера. Многие если и находили ее быстро, но в конечном итоге оказывалось что она работает не так как описано в документации, либо вообще его уже нет.
Сегодня я вам докажу, что есть способы поддерживать документацию в актуальном виде и в этом мне будет помогать Open Source framework от компании SmartBear под названием Swagger, а с 2016 года он получил новое обновление и стал называться OpenAPI Specification.

Swagger - это фреймворк для спецификации RESTful API. Его прелесть заключается в том, что он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы так называемый Swagger UI.

Также возможно сгенерировать непосредственно клиента или сервер по спецификации API Swagger, для этого понадобиться Swagger Codegen.

Основные подходы

Swagger имеет два подхода к написанию документации:

  • Документация пишется на основании вашего кода.

    • Данный подход позиционируется как "очень просто". Нам достаточно добавить несколько зависимостей в проект, добавить конфигурацию и уже мы будем иметь нужную документацию, хоть и не настолько описанной какою мы хотели.

    • Код проекта становиться не очень читабельным от обилия аннотаций и описания в них.

    • Вся документация будет вписана в нашем коде (все контроллеры и модели превращаются в некий Java Swagger Code)

    • Подход не советуют использовать, если есть возможности, но его очень просто интегрировать.

  • Документация пишется отдельно от кода.

    • Данный подход требует знать синтаксис Swagger Specification.

    • Документация пишется либо в JAML/JSON файле, либо в редакторе Swagger Editor.

Swagger Tools

Swagger или OpenAPI framework состоит из 4 основных компонентов:

  1. Swagger Core - позволяет генерировать документацию на основе существующего кода основываясь на Java Annotation.

  2. Swagger Codegen - позволит генерировать клиентов для существующей документации.

  3. Swagger UI - красивый интерфейс, который представляет документацию. Дает возможность просмотреть какие типы запросов есть, описание моделей и их типов данных.

  4. Swagger Editor - Позволяет писать документацию в YAML или JSON формата.

Теперь давайте поговорим о каждом компоненте отдельно.

Swagger Core

Swagger Code - это Java-реализация спецификации OpenAPI

Для того что бы использовать Swagger Core во все орудие, требуется:

  • Java 8 или больше

  • Apache Maven 3.0.3 или больше

  • Jackson 2.4.5 или больше

Что бы внедрить его в проект, достаточно добавить две зависимости:

<dependency>    <groupId>io.swagger.core.v3</groupId>    <artifactId>swagger-annotations</artifactId>    <version>2.1.6</version></dependency><dependency>    <groupId>org.springdoc</groupId>    <artifactId>springdoc-openapi-ui</artifactId>    <version>1.5.2</version></dependency>

Также можно настроить maven плагин, что бы наша документация при сборке проект генерировалсь в YAML

<plugin> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-maven-plugin</artifactId> <version>0.3</version> <executions>   <execution>   <phase>integration-test</phase>   <goals>     <goal>generate</goal>   </goals>   </execution> </executions> <configuration>   <apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>   <outputFileName>openapi.yaml</outputFileName>   <outputDir>${project.build.directory}</outputDir> </configuration></plugin>

Дальше нам необходимо добавить конфиг в проект.

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

    @Bean    public GroupedOpenApi publicUserApi() {        return GroupedOpenApi.builder()                             .group("Users")                             .pathsToMatch("/users/**")                             .build();    }    @Bean    public OpenAPI customOpenApi(@Value("${application-description}")String appDescription,                                 @Value("${application-version}")String appVersion) {        return new OpenAPI().info(new Info().title("Application API")                                            .version(appVersion)                                            .description(appDescription)                                            .license(new License().name("Apache 2.0")                                                                  .url("http://springdoc.org"))                                            .contact(new Contact().name("username")                                                                  .email("test@gmail.com")))                            .servers(List.of(new Server().url("http://localhost:8080")                                                         .description("Dev service"),                                             new Server().url("http://localhost:8082")                                                         .description("Beta service")));    }

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

Вот некоторые из них:

  • @Operation - Описывает операцию или обычно метод HTTP для определенного пути.

  • @Parameter - Представляет один параметр в операции OpenAPI.

  • @RequestBody - Представляет тело запроса в операции

  • @ApiResponse - Представляет ответ в операции

  • @Tag - Представляет теги для операции или определения OpenAPI.

  • @Server - Представляет серверы для операции или для определения OpenAPI.

  • @Callback - Описывает набор запросов

  • @Link - Представляет возможную ссылку времени разработки для ответа.

  • @Schema - Позволяет определять входные и выходные данные.

  • @ArraySchema - Позволяет определять входные и выходные данные для типов массивов.

  • @Content - Предоставляет схему и примеры для определенного типа мультимедиа.

  • @Hidden - Скрывает ресурс, операцию или свойство

Примеры использования:

@Tag(name = "User", description = "The User API")@RestControllerpublic class UserController {}
    @Operation(summary = "Gets all users", tags = "user")    @ApiResponses(value = {            @ApiResponse(                    responseCode = "200",                    description = "Found the users",                    content = {                            @Content(                                    mediaType = "application/json",                                    array = @ArraySchema(schema = @Schema(implementation = UserApi.class)))                    })    })    @GetMapping("/users")    public List<UserApi> getUsers()

Swagger Codegen

Swagger Codegen - это проект, который позволяет автоматически создавать клиентские библиотеки API (создание SDK), заглушки сервера и документацию с учетом спецификации OpenAPI.

В настоящее время поддерживаются следующие языки / фреймворки:

  • API clients:

    • Java (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured)

    • Kotlin

    • Scala (akka, http4s, swagger-async-httpclient)

    • Groovy

    • Node.js (ES5, ES6, AngularJS with Google Closure Compiler annotations)

    • Haskell (http-client, Servant)

    • C# (.net 2.0, 3.5 or later)

    • C++ (cpprest, Qt5, Tizen)

    • Bash

  • Server stub:

    • Java (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, RestEasy, Play Framework, PKMST)

    • Kotlin

    • C# (ASP.NET Core, NancyFx)

    • C++ (Pistache, Restbed)

    • Haskell (Servant)

    • PHP (Lumen, Slim, Silex, Symfony, Zend Expressive)

    • Python (Flask)

    • NodeJS

    • Ruby (Sinatra, Rails5)

    • Rust (rust-server)

    • Scala (Finch, Lagom, Scalatra)

  • API documentation generators:

    • HTML

    • Confluence Wiki

  • Other:

    • JMeter

Что бы внедрить его в проект, достаточно добавить зависимость, если используете Swagger:

<dependency>    <groupId>io.swagger</groupId>    <artifactId>swagger-codegen-maven-plugin</artifactId>    <version>2.4.18</version></dependency>

и если используете OpenApi 3.0, то:

<dependency>    <groupId>io.swagger.codegen.v3</groupId>    <artifactId>swagger-codegen-maven-plugin</artifactId>    <version>3.0.24</version></dependency>

Можно настроить maven плагин, и уже на процессе сборки мы можем сгенерировать нужный для нас клиент либо мок сервиса.

      <plugin>        <groupId>org.openapitools</groupId>        <artifactId>openapi-generator-maven-plugin</artifactId>        <version>3.3.4</version>        <executions>          <execution>            <phase>compile</phase>            <goals>              <goal>generate</goal>            </goals>            <configuration>              <generatorName>spring</generatorName>              <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>              <output>${project.build.directory}/generated-sources</output>              <apiPackage>com.api</apiPackage>              <modelPackage>com.model</modelPackage>              <supportingFilesToGenerate>                ApiUtil.java              </supportingFilesToGenerate>              <configOptions>                <groupId>${project.groupId}</groupId>                <artifactId>${project.artifactId}</artifactId>                <artifactVersion>${project.version}</artifactVersion>                <delegatePattern>true</delegatePattern>                <sourceFolder>swagger</sourceFolder>                <library>spring-mvc</library>                <interfaceOnly>true</interfaceOnly>                <useBeanValidation>true</useBeanValidation>                <dateLibrary>java8</dateLibrary>                <java8>true</java8>              </configOptions>              <ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>            </configuration>          </execution>        </executions>      </plugin>

Также все это можно выполнить с помощью командной строки.

Запустив джарник codegen и задав команду help можно увидеть команды, которые предоставляет нам Swagger Codegen:

  • config-help - Справка по настройке для выбранного языка

  • generate - Сгенерировать код с указанным генератором

  • help - Отображение справочной информации об openapi-generator

  • list - Перечисляет доступные генераторы

  • meta - Генератор для создания нового набора шаблонов и конфигурации для Codegen. Вывод будет основан на указанном вами языке и будет включать шаблоны по умолчанию.

  • validate - Проверить спецификацию

  • version - Показать информацию о версии, используемую в инструментах

Для нас самые нужные команды это validate, которая быстро проверять на валидность спецификации и generate,с помощью которой мы можем сгенерировать Client на языке Java

  • java -jar openapi-generator-cli-4.3.1.jar validate -i openapi.yaml

  • java -jar openapi-generator-cli-4.3.1.jar generate -i openapi.yaml -g java --library jersey2 -o client-gener-new

Swagger UI

Swagger UI - позволяет визуализировать ресурсы API и взаимодействовать с ними без какой-либо логики реализации. Он автоматически генерируется из вашей спецификации OpenAPI (ранее известной как Swagger), а визуальная документация упрощает внутреннюю реализацию и использование на стороне клиента.

Вот пример Swagger UI который визуализирует документацию для моего pet-project:

Нажавши на кнопку "Try it out", мы можем выполнить запрос за сервер и получить ответ от него:

Swagger Editor

Swagger Editor - позволяет редактировать спецификации Swagger API в YAML внутри вашего браузера и просматривать документацию в режиме реального времени. Затем можно сгенерировать допустимые описания Swagger JSON и использовать их с полным набором инструментов Swagger (генерация кода, документация и т. Д.).

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

  1. openapi

  2. info

  3. servers

  4. paths

  5. components

  6. security

  7. tags

  8. externalDocs

Для работы над документацией со спецификацией используется онлайн-редактор Swagger Редактор Swagger имеет разделенное представление: слева пишем код спецификации, а справа видим полнофункциональный дисплей Swagger UI. Можно даже отправлять запросы из интерфейса Swagger в этом редакторе.

Редактор Swagger проверит контент в режиме реального времени, и укажет ошибки валидации, во время кодирования документа спецификации. Не стоит беспокоиться об ошибках, если отсутствуют X-метки в коде, над которым идет работа.

Первым и важным свойством для документации это openapi. В объекте указывается версия спецификации OpenAPI. Для Swagger спецификации это свойство будет swagger:

 openapi: "3.0.2"

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

info:  title: "OpenWeatherMap API"  description: "Get the current weather, daily forecast for 16 days, and a three-hour-interval forecast for 5 days for your city."  version: "2.5"  termsOfService: "https://openweathermap.org/terms"  contact:  name: "OpenWeatherMap API"    url: "https://openweathermap.org/api"    email: "some_email@gmail.com"  license:    name: "CC Attribution-ShareAlike 4.0 (CC BY-SA 4.0)"    url: "https://openweathermap.org/price"

Объект servers указывает базовый путь, используемый в ваших запросах API. Базовый путь - это часть URL, которая находится перед конечной точкой. Объект servers обладает гибкой настройкой. Можно указать несколько URL-адресов:

servers:  - url: https://api.openweathermap.org/data/2.5/        description: Production server  - url: http://beta.api.openweathermap.org/data/2.5/        description: Beta server  - url: http://some-other.api.openweathermap.org/data/2.5/        description: Some other server

paths - Это та же конечная точка в соответствии с терминологии спецификации OpenAPI. Каждый объект path содержит объект operations - это методы GET, POST, PUT, DELETE:

paths:  /weather:     get:

Объект components уникален среди других объектов в спецификации OpenAPI. В components хранятся переиспользуемые определения, которые могут появляться в нескольких местах в документе спецификации. В нашем сценарии документации API мы будем хранить детали для объектов parameters и responses в объекте components

Conclusions

  • Документация стала более понятней для бизнес юзера так и для техническим юзерам (Swagger UI, Open Specifiation)

  • Может генерировать код для Java, PHP, .NET, JavaScrypt (Swager Editor, Swagger Codegen)

  • Можно проверять насколько совместимы изменения. Можно настраивать это в дженкинсе

  • Нет ни какой лишней документации к коде, код отдельно, документация отдельно

Подробнее..
Категории: Java , Api , Spring , Swagger , Openapi

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru