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

Как разработчики передают друг другу знания об API

Как разработчики передают друг другу знания об API

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

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

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

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

  1. Данные должны быть объектом.

  2. Этот объект должен содержать два свойства «id» и «name».

  3. Значение свойства «id» должно иметь тип «integer».

  4. Значение свойства «name» должно иметь тип «string».

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

Неформальные пометки в примерах данных

Неформальные пометки в примерах данных

Этот неформальный способ тоже можно легко формализовать. Мы решили, что будем помещать после знака комментария маленький 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 не пожелал

    © Habrahabr.ru