
Всем привет! Я Ира — тимлид команды, которая занимается развитием и поддержкой публичного API hh.ru.
Несколько лет назад к нам пришли ребята из мобильной команды и сказали, что хотят документацию в OpenAPI формате. Для них это удобно, потому что можно генерировать DTO прямо из документации и не писать их руками. В то время у нас было выставлено около 100 URL, некоторые из них достаточно сложные. Например, в нашей документации у резюме есть три формата: нано, микро и полное. Каждый формат расширяет следующий, но теоретически могут быть и какие-то отличия, и это надо было изучать. В полном формате резюме более 30 полей и некоторые из них представлены в виде объектов. И такой URL у нас не один. Мы знали, что перевод документации на OpenAPI займет очень много времени, и в процессе мы, вероятно, столкнемся с трудностями (о них ниже), но при этом также понимали, что в долгосрочной перспективе мы получим от этого и много плюсов, поэтому решили попробовать.
Какие плюсы мы видели в переходе на OpenAPI:
-
Возможность генерировать DTO
-
Возможность проверять документацию на соответствие действительности: в Markdown-документации у нас часто бывали случаи, когда объект, описанный в ней, не соответствует тому, что на самом деле возвращает/принимает метод (обязательное поле описано необязательным, какое-то поле отсутствует и другое)
-
Стандартизация написания документации: все будет написано по единым правилам
-
Возможность генерировать клиенты для различных языков
-
Упрощение написания некоторых автоматизаций/преобразований/проверок документации, так как формат стандартизирован
Какие минусы:
-
Перевод нашей документации требует большого количества времени
-
Зависимость от одной из оболочек, которые умеют отображать OpenAPI, а они слабо кастомизируются, либо надо писать что-то свое, что требует много времени и трудозатраты на поддержку
-
Нет стандартного решения для генерации документации на других языках
Наш путь
Выбор варианта реализации
При написании OpenAPI-документации есть два варианта:
-
Писать YML-файлы вручную
-
Генерировать документацию из кода
Плюсы написания YML-файлов вручную:
-
может писать не только разработчик
-
не появляется лишних сущностей при генерации
-
можно написать гибко, например, переиспользовать сущность, используя oneOf, anyOf, использовать discriminator, указать где ожидаются дополнительные поля, а где нет и т.д.
Минусы написания YML-файлов вручную:
-
сложнее, чем сгенерировать из кода (нужно изучить, в каком формате писать YML-файлы), нужно тратить на это дополнительное время
-
документация может разойтись с кодом (если не генерировать код на основании нее)
Плюсы генерации:
-
не нужно изучать “новый язык”
-
документация не разойдется с кодом
Минусы генерации:
-
нет должной гибкости
-
сложно добавить большое описание
-
код перегружается информацией для документации
-
не для всех фреймворков легко подключить генерацию
Мы выбрали первый вариант, потому что не было готового механизма генерации YML-файлов из кода для нашей реализации сервиса (у нас проект на python на самописной библиотеке поверх tornado). Также для нас большой плюс написания YML-файлов в том, что их может подготовить любой член команды, а не только разработчик (сейчас нас это сильно спасает, так как Markdown в YML переводит подрядчик, который не знаком с программированием).
Как мы сделали в результате
-
Переводим один метод из Markdown-файла в YML (описываем его руками)
-
Переводим автотесты на автосгенерированные DTO. Это нужно, чтобы проверить, что мы не допустили ошибок в описании документации (полностью проверить пока не получается, но ошибок явно меньше, чем в Markdown-документации)
С какими еще сложностями мы столкнулись и как их решали
-
У нас есть служебные методы и поля, которые нужны только нашим мобильным приложениям, то есть нужно две документации, чтобы не засорять основную служебными методами
-
У нас есть английская документация
Для решения проблем со служебной и английской документациями мы написали скрипты, которые формируют две новые документации на основе русской. Точнее так: в начале генерируется служебная, потом из нее удаляются служебные методы и поля — получается публичная. Для формирования английской есть файлы с переводами, которые накатываются при формировании документации. В результате мы все объекты описываем один раз, а потом скриптом делаем “магию”, и появляется три версии документации. В Markdown-документации приходилось дублировать описание URL, и английская документация очень часто отставала от русской, потому что, например, кто-то поправил в русской, забыл в английской, и документации разошлись. В какой-то момент мы поняли, что это проблема, и написали скрипты проверки пул-реквестов для Markdown-документации, но в любом случае с OpenAPI решение выглядит лучше. Если забыть добавить перевод в OpenAPI-документацию, то изменения долетят, просто отсутствующий перевод будет на русском языке. И это лучше, чем несоответствие документации с реальностью.
-
Python библиотека, которую мы используем для верификации входящего объекта на соответствие OpenAPI схемы, не всегда выдает подробные ошибки, а мы стараемся выдавать их максимально точными, для того чтобы сократить количество вопросов нашей поддержке.
Для решения проблемы верификации входящего объекта пришлось немного написать кода поверх стандартного валидатора (мы используем openapi-schema-validator). Если погружаться в подробности: проблемы возникают только на сложных объектах, где присутствуют oneOf, anyOf и discriminator. В этом случае если валидатор не находит подходящий объект, он выдает абстрактную ошибку о том, что нет подходящего объекта. В то же время мы понимаем, что можно выдать что-то более точное, например, что нет какого-то обязательного поля в конкретном объекте или что-то подобное. Мы написали дополнительный код для таких случаев, который валидирует по каждой схеме нужный объект и выбирает наиболее подходящую ошибку. Это только один из примеров, есть еще некоторые проблемы, которые мы тоже обходили написанием дополнительных обвязок.
-
Примеры, которые генерируются Redoc на основе поля example, не всегда генерировались правильно
Такие примеры мы описываем в виде JSON-объектов в отдельном файле. Они валидируются на соответствие документации отдельным линтером.
Подведем итоги
-
Сейчас у нас есть YML-файлы, из которых генерируется несколько документаций: открытая, служебная, английская. В результате метод описывается один раз, и разные виды документации не расходятся.
-
Валидация входящих объектов происходит на основе OpenAPI-документации. Теперь не нужно каждый раз писать валидацию всех JSON полей руками, это теперь делается одной строкой.
-
Есть автотесты, в которых используются DTO, сгенерированные из документации. Это позволяет сильно уменьшить вероятность несоответствия документации действительности.
-
Любой пользователь API может сгенерировать DTO на основе спецификации. Это уменьшает время разработки при использовании нашего API.
-
Валидирование документации на соответствие принятых у нас правил (snake_case, id строка и пр) осуществляется линтером Spectral. Это уменьшает вероятность, что на прод просочатся данные в неправильном формате (раньше это обеспечивалось только внимательностью ревьювера).
-
Есть плагин, который проверяет изменения документации на обратную совместимость. Сообщения об обратно несовместимых изменениях прилетают в Mattermost. Это уменьшает вероятность того, что мы что-то сломаем нашим пользователям.
-
Сейчас переведено около 95% публичной документации. В планах закончить перевод до конца этого года.
Что дальше?
-
Попробовать генерировать документацию из кода, так как разработчикам не нравится писать YML-файлы. Также не очень удобно, что файл лежит в одном месте, а код в другом и надо потратить какое-то время, чтобы найти нужное место в документации.
-
Попробовать генерировать клиенты для различных языков (периодически нас спрашивают об этом пользователи API).
-
Решить проблему расхождения документации и реальности. Да, эта проблема все еще, к сожалению, остается. В меньших масштабах, но она есть.
ссылка на оригинал статьи https://habr.com/ru/articles/777750/
Добавить комментарий