Что не так с 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-спецификацию на основе программного кода сервиса. И это, действительно, прекрасный способ, но, к сожалению, далеко не во всех проектах его удаётся применить, и вот почему:

  1. Во многих (особенно, в крупных) проектах, в проектировании API, помимо разработчиков, участвуют ещё несколько человек: архитекторы, системные аналитики, QA-инженеры, заинтересованные лица из других команд. На таком совещании запросто могут присутствовать 10 человек, и из них далеко не все умеют или хотят генерировать OpenAPI-документы из программного кода. Обычно приходится писать OpenAPI-фалы вручную, обмениваться ими, комментировать, вносить правки и т. д.
  2. Во многих (особенно, в крупных) проектах, используются устаревшие языки или фреймворки, для которых просто не существует генераторов OpenAPI-спецификаций. В итоге, часть API оказывается без документации (и как правило это очень важные API, работающие в компании уже много лет!).
  3. Генераторы OpenAPI-спецификаций нередко содержат ошибки, которые порой не позволяют сгенерировать то, что нужно (вот пример бага в генераторе). Особенно это проявляется в крупных долгоживущих проектах, когда разные части системы, как правило, написаны на разных языках и разных фреймворках. Для каждого фреймворка используется свой генератор OpenAPI, и вероятность того, что один из этих генераторов даст сбой, повышается. Если генератор OpenAPI ломается, то этот API остаётся без документации. Вручную документировать этот API, конечно, никто не будет.
  4. OpenAPI-генераторы способны сгенерировать только часть документации. Но ведь надо еще добавить подробные описания, примеры использования, примеры сообщений (особенно это важно в крупных проектах). Всё это нужно делать вручную и каким-то образом заталкивать в программный код, чтобы потом, на его основе, генератор создал полноценную спецификацию.

Интересно, что все перечисленные недостатки генераторов проявляются в большей степени в крупных проектах. Именно поэтому в больших системах практически всегда используют подход “Spec First”.

Подведём итог (цифры даны примерные):

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

Это удивительно, насколько серьёзные последствия происходят из такой, казалось бы, незначительной вещи, как «неудобный язык описания API»! Из-за этой «мелочи» разработчики упорно саботируют документирование API, в результате чего огромное число существующих API описано плохо, либо не описано вовсе.

Во всём сказанном мы убедились на собственном опыте. За несколько лет мы перепробовали все возможные варианты документирования API: на некоторых проектах мы писали OpenAPI-документы вручную, на некоторых создавали их генераторами, на некоторых отпускали весь процесс на самотёк.

Однажды мы решили, что больше так жить не хотим.

Мы были уверены, что существует какой-то другой, более простой способ описывать API. Однако то, к чему мы в итоге пришли, удивило даже нас.

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

Что не так с OpenAPI?

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

Можно ли использовать пример данных для формального описания схемы данных? Конечно! Нужно только договориться о формальных правилах интерпретации примера данных. В большинстве случаев эти правила считываются вполне интуитивно. Возьмём всё тот же пример данных:

{ "id": 123, "name": "Tom" }

Этот пример интуитивно считывается нами в виде следующих четырёх требований к структуре данных:

  1. Данные должны быть объектом.
  2. Этот объект должен содержать два свойства "id" и "name".
  3. Значение свойства "id" должно иметь тип "integer".
  4. Значение свойства "name" должно иметь тип "string".

Возникает вопрос: а если захочется добавить к этим требованиям ещё какое-нибудь «хитрое» требование, которое нельзя выразить с помощью примера данных? Например, если мы захотим сказать, что свойство "name" в этом объекте не обязательное? Опять идём к программистам и подсматриваем, как они это делают. В этом случае к примеру данных они добавляют всем привычные комментарии в свободной форме:

Что не так с OpenAPI?

Этот неформальный способ тоже можно легко формализовать. Мы решили, что будем помещать после знака комментария маленький json-объект с описанием всех дополнительных требований к соответствующему свойству. Получилось так:

{ "id": 123, "name": "Tom" // {optional: true} }

Шикарно!

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

  1. Язык должен быть максимально интуитивно понятным.
  2. Язык должен максимально задействовать уже знакомые разработчикам термины и конструкции.
  3. Язык должен уметь описывать не только REST API, но и все другие популярные виды API (например, JSON-RPC). Это позволит переиспользовать готовые библиотеки типов в разных видах API.
  4. Язык должен быть полностью совместим с языком OpenAPI (чтобы потом не было трудностей с созданием конвертера в OpenAPI и обратно).

Через пару лет работы мы получили довольно симпатичный язык. Вот, например, описание приведённого выше API (в самом начале статьи):

JSIGHT 0.3 GET /users/{id} 200 { "id" : 123, "name": "Tom" }

Первая строка "JSIGHT 0.3" — это название и версия языка. В остальном всё должно быть понятно без комментариев. Напомним, на OpenAPI то же самое описание заняло 23 строки.

В дополнение к языку мы разработали минимальный набор инструментов:

  • редактор с подсветкой синтаксиса;
  • генератор HTML-документации (чтобы получился красивый документ);
  • валидатор сообщений (чтобы все входящие и исходящие сообщения проверялись на соответствие спецификации API).

Мы попробовали всё это в трёх наших проектах, и вот что уже можно сказать:

  • Описание API, кажется, больше не приносит страданий. По крайней мере ни один разработчик возвращаться на OpenAPI не пожелал 🙂
  • Ура! На проектах 100% точная, свежая, актуальная документация API (спасибо валидатору сообщений, который заставляет разработчика поправлять спецификацию API, если она хоть немножко устарела и не соответствует реальности).
  • Валидатор легко встроился во все фреймворки, в том числе в legacy-код (мы написали адаптеры для C/C++, Go, PHP, Python, Java, NodeJS, Lua).

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

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

Теперь проектирование API происходит у нас ровно за одно совместное совещание. С OpenAPI такое нельзя даже представить — на нём невозможно создавать быстрые эскизы. Раньше совещания происходили в 2–3 и более итерации, в промежутках между которыми аналитик фиксировал идеи на OpenAPI — так, как он их понял во время предыдущего совещания. После этого он рассылал OpenAPI-файл всем участникам, и (ну конечно же!) оказывалось, что участники друг друга неверно поняли, и нужно собираться снова. Получилось, что новый язык изменил наш привычный бизнес-процесс дизайна API.

И всё-таки, мы признаём, что несколько сошли с ума. Придумывать новый язык в наше время — это, прямо, что-то экстраординарное. По крайней мере для REST API весь мир уже использует язык OpenAPI. Захотят ли люди переходить на что-то новое, если уже привыкли работать на старом?

Непростой вопрос. Вся история IT-индустрии говорит о том, что сложные и машинно-ориентированные технологии обязательно уступают место простым и интуитивно-понятным. Если разработчику больно работать с чем-то, то рано или позно появится замена. Так когда-то произошёл переход от XML к JSON. Так когда-то SOAP уступил место REST. Так, скорее всего, случится и с OpenAPI. Но когда это случиться? Каким образом это произойдёт? Что придёт на смену?

Может ли наша безумная попытка претендовать на преемника OpenAPI?

Мы искренне верим в это! Но, как говорится, будущее покажет.

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

2020
23 комментария

Комментарий недоступен

2

Конечно, мы не из них) Это просто пример. По поводу семантики — мы очень запариваемся. Вот, например, как мучительно мы выбирали между `optional: true` и `required: false`: https://jsight.io/blog/convenience-or-habit-required-vs-optional

2

404 статус это нету сущности и по какой то причине ты стучишься в другой сервер?
А че делать, если решишь апи не через http гонять?

Комментарий недоступен

2

Спасибо, Александр) Поможете нам, если поставите звезду на гитхабе. https://github.com/jsightapi/online-editor-frontend

2

Пишу на FastAPI. Документация создаётся автоматически при описании эндпойнтов в коде. ЧЯДНТ?))

2

Я не знаю точных возможностей FastAPI по генерации документации, но предположу основные ограничения, универсальные для такого подхода:

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

2. Вы доверяете генератору в своей фреймворке. Где-то они работают относительно надежно, где-то нет, т.е. подход сам по себе ненадежный, если смотреть глобально.

3. Вы используете только этот фреймворк и язык. Захочется поменять язык или добавить в АПИ сервис на другом языке, появится много ручного труда, собирание документации из разных источников, а описанные выше проблемы умножатся на 2.

2