Как мы боролись с документированием 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-спецификации. Вместо этого они рисуют или отправляют друг другу примеры данных, которые нужно отправить в тот или иной ендпоинт (или получить в ответ). То есть переписка выглядит буквально так:
Показать пример — это самый простой и натуральный способ передачи знаний. Видимо, так устроен наш
Можно ли использовать пример данных для формального описания схемы данных? Конечно! Нужно только договориться о формальных правилах интерпретации примера данных. В большинстве случаев эти правила считываются вполне интуитивно. Возьмём всё тот же пример данных:
{
"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 не пожелал 🙂
-
Ура! На проектах 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?
Мы искренне верим в это! Но, как говорится, будущее покажет.
Мы надеемся, что эта статья была интересна. Мы затронули сложные, неодназначные вопросы, на которые пока еще не существует общепринятных ответов. Будем рады вашим комментариям и мыслям. Если любытно, можете посмотреть наш код на гитхабе, мы всё выложили в опенсорс.
Автор: Константин Малышев