Что не так с OpenAPI?
Как мы боролись с документированием API на наших проектах, и как мы немного сошли с ума
У вас на проекте порядок с документацией на API? Скорее всего нет. И в нашей компании порядка не было.
Не будем рассказывать, к каким печальным последствиям приводит ошибочная, устаревшая или вовсе отсутствующая API-документация. Почему же на большинстве проектов не удаётся решить такой, казалось бы, несложный вопрос?
Причина проста: разработчики терпеть не могут описывать API. Это неудобно, это мучительно, этого никогда не хочется делать. И даже если начальник однажды заставит (мольбами и угрозами) написать документацию на первую версию API, то в дальнейшем, когда API изменится, обновлять документацию на него разработчик уже точно не будет.
Например, OpenAPI — самый распространённый язык описания REST API. Писать на этом языке настолько больно, что разработчик никогда не упустит шанс этого не делать (вот хороший пример отношения к OpenAPI).
Возьмём простейший API с одним ендпоинтом:
GET /users/{id}
Пусть этот ендпоинт возвращает ответ с кодом 200 такого вида:
{
"id": 123,
"name": "Tom"
}
В этом случае программист вынужден написать на языке OpenAPI 23 строки (!):
openapi: 3.0.1
paths:
/users/{id}:
get:
parameters:
- name: id
in: path
required: true
schema: {}
responses:
200:
content:
application/json:
schema:
required: [id, name]
type: object
properties:
id:
type: integer
example: 123
name:
type: string
example: "Tom"
Реальные API частенько занимают не 23 строки, а несколько тысяч строк (вот, для примера, API Твиттера на 13 тысяч строк).
Существуют даже специальные инструменты — визуальные редакторы OpenAPI, которые позволяют писать OpenAPI-код не руками, а с помощью графического пользовательского интерфейса (например, https://stoplight.io/). Правда, эти инструменты не слишком помогают: вместо того, чтобы набирать много строк OpenAPI-кода, теперь нужно много кликать мышкой.
В результате, в реальных проектах происходит следующее (приводим усреднённые данные из нескольких исследований):
Примерно в половине проектов API не описывают вообще.
Примерно в 25% проектов API описывают вручную (т. н. подход «Spec First»). При этом во многих проектах написанную однажды документацию в актуальном состоянии не поддерживают.
Примерно в 25% проектов документацию генерируют из программного кода (т. н. подход «Code First»).
Последний вариант, на первый взгляд, кажется замечательным решением. Для многих фреймворков существуют генераторы, которые автоматически создают OpenAPI-спецификацию на основе программного кода сервиса. И это, действительно, прекрасный способ, но, к сожалению, далеко не во всех проектах его удаётся применить, и вот почему:
Во многих (особенно, в крупных) проектах, в проектировании API, помимо разработчиков, участвуют ещё несколько человек: архитекторы, системные аналитики, QA-инженеры, заинтересованные лица из других команд. На таком совещании запросто могут присутствовать 10 человек, и из них далеко не все умеют или хотят генерировать OpenAPI-документы из программного кода. Обычно приходится писать OpenAPI-фалы вручную, обмениваться ими, комментировать, вносить правки и т. д.
Во многих (особенно, в крупных) проектах, используются устаревшие языки или фреймворки, для которых просто не существует генераторов OpenAPI-спецификаций. В итоге, часть API оказывается без документации (и как правило это очень важные API, работающие в компании уже много лет!).
Генераторы OpenAPI-спецификаций нередко содержат ошибки, которые порой не позволяют сгенерировать то, что нужно (вот пример бага в генераторе). Особенно это проявляется в крупных долгоживущих проектах, когда разные части системы, как правило, написаны на разных языках и разных фреймворках. Для каждого фреймворка используется свой генератор OpenAPI, и вероятность того, что один из этих генераторов даст сбой, повышается. Если генератор OpenAPI ломается, то этот API остаётся без документации. Вручную документировать этот API, конечно, никто не будет.
OpenAPI-генераторы способны сгенерировать только часть документации. Но ведь надо еще добавить подробные описания, примеры использования, примеры сообщений (особенно это важно в крупных проектах). Всё это нужно делать вручную и каким-то образом заталкивать в программный код, чтобы потом, на его основе, генератор создал полноценную спецификацию.
Интересно, что все перечисленные недостатки генераторов проявляются в большей степени в крупных проектах. Именно поэтому в больших системах практически всегда используют подход «Spec First».
Подведём итог (цифры даны примерные):
Более 50% проектов страдают от отсутствующей или устаревшей документации API.
В 25% проектов сотрудники страдают от необходимости писать OpenAPI-файлы вручную (в основном это крупные проекты).
В 25% проектов проблема решена с помощью генераторов. Однако, это решение обладает недостатками и ограничениями, которые не позволяют использовать его в остальных 75% проектов.
Это удивительно, насколько серьёзные последствия происходят из такой, казалось бы, незначительной вещи, как «неудобный язык описания API»! Из-за этой «мелочи» разработчики упорно саботируют документирование API, в результате чего огромное число существующих API описано плохо, либо не описано вовсе.
Во всём сказанном мы убедились на собственном опыте. За несколько лет мы перепробовали все возможные варианты документирования API: на некоторых проектах мы писали OpenAPI-документы вручную, на некоторых создавали их генераторами, на некоторых отпускали весь процесс на самотёк.
Однажды мы решили, что больше так жить не хотим.
Мы были уверены, что существует какой-то другой, более простой способ описывать API. Однако то, к чему мы в итоге пришли, удивило даже нас.
Если посмотреть, как программисты рассказывают друг другу о структуре API в обычной рабочей обстановке, то вы никогда не увидите, чтобы они рисовали на досках или отправляли друг другу в чате что-то похожее на OpenAPI-спецификации. Вместо этого они рисуют или отправляют друг другу примеры данных, которые нужно отправить в тот или иной ендпоинт (или получить в ответ). То есть переписка выглядит буквально так:
Как разработчики передают друг другу знания об API
Показать пример — это самый простой и натуральный способ передачи знаний. Видимо, так устроен наш мозг. Можно долго, подробно и запутанно что-то объяснять, а потом показать пример — и всё сразу встаёт на свои места.
Можно ли использовать пример данных для формального описания схемы данных? Конечно! Нужно только договориться о формальных правилах интерпретации примера данных. В большинстве случаев эти правила считываются вполне интуитивно. Возьмём всё тот же пример данных:
{
"id": 123,
"name": "Tom"
}
Этот пример интуитивно считывается нами в виде следующих четырёх требований к структуре данных:
Данные должны быть объектом.
Этот объект должен содержать два свойства «id» и «name».
Значение свойства «id» должно иметь тип «integer».
Значение свойства «name» должно иметь тип «string».
Возникает вопрос:, а если захочется добавить к этим требованиям ещё какое-нибудь «хитрое» требование, которое нельзя выразить с помощью примера данных? Например, если мы захотим сказать, что свойство «name» в этом объекте не обязательное? Опять идём к программистам и подсматриваем, как они это делают. В этом случае к примеру данных они добавляют всем привычные комментарии в свободной форме:
Неформальные пометки в примерах данных
Этот неформальный способ тоже можно легко формализовать. Мы решили, что будем помещать после знака комментария маленький json-объект с описанием всех дополнительных требований к соответствующему свойству. Получилось так:
{
"id": 123,
"name": "Tom" // {optional: true}
}
Шикарно!
Если бы мы знали, какой огромный путь нам придётся пройти от первой идеи языка до рабочей версии, которую можно использовать в реальных проектах, мы бы пришли в ужас и поняли, что это безумие. Но, к счастью, мы не знали об этом и принялись за работу. Вот важные принципы, которые мы для себя обозначили:
Язык должен быть максимально интуитивно понятным.
Язык должен максимально задействовать уже знакомые разработчикам термины и конструкции.
Язык должен уметь описывать не только REST API, но и все другие популярные виды API (например, JSON-RPC). Это позволит переиспользовать готовые библиотеки типов в разных видах API.
Язык должен быть полностью совместим с языком OpenAPI (чтобы потом не было трудностей с созданием конвертера в OpenAPI и обратно).
Через пару лет работы мы получили довольно симпатичный язык. Вот, например, спецификация приведённого выше API (в самом начале статьи):
JSIGHT 0.3
GET /users/{id}
200
{
"id" : 123,
"name": "Tom"
}
Первая строка «JSIGHT 0.3» — это название и версия языка. В остальном всё должно быть понятно без комментариев. Напомним, на OpenAPI то же самое описание заняло 23 строки.
В дополнение к языку мы разработали минимальный набор инструментов:
редактор с подсветкой синтаксиса;
генератор HTML-документации (чтобы получился красивый документ);
валидатор сообщений (чтобы все входящие и исходящие сообщения проверялись на соответствие спецификации API).
Мы попробовали всё это в трёх наших проектах, и вот что уже можно сказать:
Описание API, кажется, больше не приносит страданий. По крайней мере ни один разработчик возвращаться на OpenAPI не пожелал