Всем привет! Я Ира — тимлид команды, которая занимается развитием и поддержкой публичного API hh.ru.

Несколько лет назад к нам пришли ребята из мобильной команды и сказали, что хотят документацию в OpenAPI формате. Для них это удобно, потому что можно генерировать DTO прямо из документации и не писать их руками. В то время у нас было выставлено около 100 URL, некоторые из них достаточно сложные. Например, в нашей документации у резюме есть три формата: нано, микро и полное. Каждый формат расширяет следующий, но теоретически могут быть и какие-то отличия, и это надо было изучать. В полном формате резюме более 30 полей и некоторые из них представлены в виде объектов. И такой URL у нас не один. Мы знали, что перевод документации на OpenAPI займет очень много времени, и в процессе мы, вероятно, столкнемся с трудностями (о них ниже), но при этом также понимали, что в долгосрочной перспективе мы получим от этого и много плюсов, поэтому решили попробовать. 

Какие плюсы мы видели в переходе на OpenAPI: 

  1. Возможность генерировать DTO

  2. Возможность проверять документацию на соответствие действительности: в Markdown-документации у нас часто бывали случаи, когда объект, описанный в ней, не соответствует тому, что на самом деле возвращает/принимает метод (обязательное поле описано необязательным, какое-то поле отсутствует и другое)

  3. Стандартизация написания документации: все будет написано по единым правилам

  4. Возможность генерировать клиенты для различных языков

  5. Упрощение написания некоторых автоматизаций/преобразований/проверок документации, так как формат стандартизирован

Какие минусы: 

  1. Перевод нашей документации требует большого количества времени

  2. Зависимость от одной из оболочек, которые умеют отображать OpenAPI, а они слабо кастомизируются, либо надо писать что-то свое, что требует много времени и трудозатраты на поддержку

  3. Нет стандартного решения для генерации документации на других языках

Наш путь 

Выбор варианта реализации 


При написании OpenAPI-документации есть два варианта: 

  1. Писать YML-файлы вручную 

  2. Генерировать документацию из кода 

Плюсы написания YML-файлов вручную:

  • может писать не только разработчик

  • не появляется лишних сущностей при генерации

  • можно написать гибко, например, переиспользовать сущность, используя oneOf, anyOf, использовать discriminator, указать где ожидаются дополнительные поля, а где нет и т.д.

Минусы написания YML-файлов вручную:

  • сложнее, чем сгенерировать из кода (нужно изучить, в каком формате писать YML-файлы), нужно тратить на это дополнительное время

  • документация может разойтись с кодом (если не генерировать код на основании нее)

Плюсы генерации:

  • не нужно изучать “новый язык”

  • документация не разойдется с кодом

Минусы генерации:

  • нет должной гибкости

  • сложно добавить большое описание

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

  • не для всех фреймворков легко подключить генерацию

Мы выбрали первый вариант, потому что не было готового механизма генерации YML-файлов из кода для нашей реализации сервиса (у нас проект на python на самописной библиотеке поверх tornado). Также для нас большой плюс написания YML-файлов в том, что их может подготовить любой член команды, а не только разработчик (сейчас нас это сильно спасает, так как Markdown в YML переводит подрядчик, который не знаком с программированием).

Как мы сделали в результате

  • Переводим один метод из Markdown-файла в YML (описываем его руками)

  • Переводим автотесты на автосгенерированные DTO. Это нужно, чтобы проверить, что мы не допустили ошибок в описании документации (полностью проверить пока не получается, но ошибок явно меньше, чем в Markdown-документации)

С какими еще сложностями мы столкнулись и как их решали 

  1. У нас есть служебные методы и поля, которые нужны только нашим мобильным приложениям, то есть нужно две документации, чтобы не засорять основную служебными методами

  2. У нас есть английская документация

Для решения проблем со служебной и английской документациями мы написали скрипты, которые формируют две новые документации на основе русской. Точнее так: в начале генерируется служебная, потом из нее удаляются служебные методы и поля — получается публичная. Для формирования английской есть файлы с переводами, которые накатываются при формировании документации. В результате мы все объекты описываем один раз, а потом скриптом делаем “магию”, и появляется три версии документации. В Markdown-документации приходилось дублировать описание URL, и английская документация очень часто отставала от русской, потому что, например, кто-то поправил в русской, забыл в английской, и документации разошлись. В какой-то момент мы поняли, что это проблема, и написали скрипты проверки пул-реквестов для Markdown-документации, но в любом случае с OpenAPI решение выглядит лучше. Если забыть добавить перевод в OpenAPI-документацию, то изменения долетят, просто отсутствующий перевод будет на русском языке. И это лучше, чем несоответствие документации с реальностью. 

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

Для решения проблемы верификации входящего объекта пришлось немного написать кода поверх стандартного валидатора (мы используем openapi-schema-validator). Если погружаться в подробности: проблемы возникают только на сложных объектах, где присутствуют oneOf, anyOf и discriminator. В этом случае если валидатор не находит подходящий объект, он выдает абстрактную ошибку о том, что нет подходящего объекта. В то же время мы понимаем, что можно выдать что-то более точное, например, что нет какого-то обязательного поля в конкретном объекте или что-то подобное. Мы написали дополнительный код для таких случаев, который валидирует по каждой схеме нужный объект и выбирает наиболее подходящую ошибку. Это только один из примеров, есть еще некоторые проблемы, которые мы тоже обходили написанием дополнительных обвязок. 

  1. Примеры, которые генерируются Redoc на основе поля example, не всегда генерировались правильно

Такие примеры мы описываем в виде JSON-объектов в отдельном файле. Они валидируются на соответствие документации отдельным линтером.   

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

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

  • Валидация входящих объектов происходит на основе OpenAPI-документации. Теперь не нужно каждый раз писать валидацию всех JSON полей руками, это теперь делается одной строкой.

  • Есть автотесты, в которых используются DTO, сгенерированные из документации. Это позволяет сильно уменьшить вероятность несоответствия документации действительности.

  • Любой пользователь API может сгенерировать DTO на основе спецификации. Это уменьшает время разработки при использовании нашего API.

  • Валидирование документации на соответствие принятых у нас правил (snake_case, id строка и пр) осуществляется линтером Spectral. Это уменьшает вероятность, что на прод просочатся данные в неправильном формате (раньше это обеспечивалось только внимательностью ревьювера).

  • Есть плагин, который проверяет изменения документации на обратную совместимость. Сообщения об обратно несовместимых изменениях прилетают в Mattermost. Это уменьшает вероятность того, что мы что-то сломаем нашим пользователям.

  • Сейчас переведено около 95% публичной документации. В планах закончить перевод до конца этого года. 

Что дальше?

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

  • Попробовать генерировать клиенты для различных языков (периодически нас спрашивают об этом пользователи API). 

  • Решить проблему расхождения документации и реальности. Да, эта проблема все еще, к сожалению, остается. В меньших масштабах, но она есть.  

Комментарии (6)


  1. rsashka
    05.12.2023 07:28

    из Markdown в OpenAPI

    А вы точно ничего не перепутали в заголовке статьи? Markdown, это документ с форматом разметки на основе простого текста, а OpenAPI, это описание интерфейса взаимодействия между системами.

    Может быть вы имели ввиду, что перешли с ручного описания интерфейса в Markdown формате на автоматическую генерацию документации согласно спецификации OpenAPI (которая так же может быть в Markdown формате)?


    1. iriss22 Автор
      05.12.2023 07:28

      Мы переводили API документацию. Поправили заголовок, спасибо за комментарий.


    1. SpectatorLife
      05.12.2023 07:28

      фух, я думал мне одному показалось, что теплое пытаются вставить в мягкое )


    1. ipodman
      05.12.2023 07:28

      Привет, нет

      Раньше документация была в Markdown в произвольном формате на гитхабе, теперь для описания используем стандарт OpenApi, а отображаем в redoc. На текущий момент сам процесс написания документации не поменялся - он так же ручной

      Речь идет именно о публичной документации к API, а не о доке к каждому сервису


  1. QRash
    05.12.2023 07:28
    +1

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


    1. iriss22 Автор
      05.12.2023 07:28
      +1

      Как было до этого можно посмотреть по истории репы https://github.com/hhru/api, что стало тут https://api.hh.ru/openapi/redoc
      Можно скачать итоговый yaml файл, но описываем мы не в одном файлике, а разбиваем. Примеры одинарных файлов хранятся в закрытой репе, но они вполне следуют правилам OpenAPI спецификации.