Привет, Хабр.

Хочу на основе одного проекта поделиться личным опытом: как от полного хаоса в разработке и управления API в виде спецификаций в Confluence мы пришли к удобному и понятному процессу со спецификациями OpenAPI и кодогенерацией.

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

В статье не будет никакой теории, все примеры и подходы взяты из моей практики, но не ищите тут открытий и откровений. Зачем я её написал? Хочу показать, что выстроить удобный процесс разработки API не сложно, и это принесёт много пользы проекту и команде.

Какие проблемы решаем?

Зачем нам вообще задаваться вопросами проектирования API? Какие есть проблемы в подходах «Написал код, описал в свободной форме фронтендеру и закрыл вопрос» и «Аналитика в свободной форме и реализация по этой аналитике»?

Попробую перечислить проблемы, как они видятся мне и от чего приходилось страдать:

  1. Когда аналитики описывают функциональность, они делают это в свободной форме: имена полей, naming conventions, обязательность, вложенность — всё это может остаться не описанным или быть описано не верно. В итоге бэкендер пишет в CamelCase, фронтендер хочет в cebab‑case, аналитик говорит: «Мне всё равно, сами решайте», а тимлид отговаривается: «Мне некогда».

  2. При разработке по спецификации от аналитика в свободной текстово-табличной форме разработчик начинает домысливать то, что не описал аналитик. И в итоге у разработчика все поля обязательные, а аналитик говорит:«Ну очевидно же, что поле „имя кота“ — необязательное».

  3. Если необходимо внести изменения в систему (например, на этапе тестирования или отладки между front и back), то эти изменения могут просто не попасть обратно в документацию. И получается что код работает и принимает совсем не те данные, что описаны в документации.

  4. Если хотим внесения изменений в новые версии API, то часто оказывается, что когда добавляем обязательное поле и все клиенты, которые ходили по старой спецификации, перестают работать.

  5. Для публичного API при согласовании с потребителями возникает очень долгий и нудны итерационный процесс обмена требованиями и правками, каждая команда по-своему видит, как должна выглядеть реализация по документации.

  6. И если API предназначен для интеграции с внешним потребителем, то критичность всех проблем (согласованность, актуальность, версионирование, полнота описания) вырастает в разы, и команда большую часть своего времени занимается выяснением отношений и поиском ответов на вопросы «кто прав».

Думаю, что все сталкивались хотя бы с одной их этих проблем в своей практике. Мы столкнулись с ними как со следствием роста и развития своего продукта.

Что у нас было в проекте в начале пути? Всё просто и банально:

  • Для интеграционных API: аналитика велась в Conflunce в таблицах, и бэкендеры писали код по ней.

  • Для frontend API не было ничего: всё в чатиках, внутренних договорённостях и примерах в переписке.

Знакомая история?

Первый шаг: хотим стандарт!

Очевидно, что эти проблемы уже возникали раньше и, наверное, есть стандартные подходы к их решению. Самый распространённый подход, и единственный правильный: это использование формализованных, машинно-читаемых стандартов описания API.

Для наглядности рассмотрим взаимодействия, основанные на JSON-формате, как наиболее часто применяемые на практике.

Для описания форматов, протоколов, взаимодействий существуют три основных стандарта описания API (их больше, но это наиболее распространённые):


Описание тела сообщения

Описание синхронных взаимодействий (классические REST/HTTP)

Описание асинхронных взаимодействий (WebSocket, Kafka и т. п.)

Ссылка на стандарт

JSON Schema

Да

Нет

Нет

https://json-schema.org

OpenAPI

Да

Да

Да, через расширение стандарта

https://www.openapis.org

AsyncAPI

Да

Нет

Да

https://www.asyncapi.com

Для себя в нашем продукте мы выбрали OpenAPI. Почему?

  • Зрелый стандарт: стабильность, большое сообщество, много инструментов кодогенерации, широкая поддержка средами разработки. Это важно, когда стандарт не меняется и на любой вопрос и потребность можно найти ответ и инструмент.

  • Универсальность: можно использовать как для синхронных, так и для асинхронных взаимодействий. Нам не нужны несколько инструментов под разные взаимодействия, особенно если модель данных переиспользуется между ними.

  • Хорошо знаком разработчикам (как бэкендерам, так и фронтендерам), тестировщикам и аналитикам. OpenAPI это стандарт де‑факто для индустрии.

Шаг второй: а как написать спецификацию?

Мы выбрали стандарт описания API — это хорошо, но не решило наши проблемы. Скорее, просто нашли инструмент.

Теперь поговорим о том, как аналитики могут донести постановку до разработки и в итоге до прода, и не потерять ничего по пути?

Мы прошли три этапа и сломали несколько граблей о подводные камни:

  1. Code‑first: когда в начале пишем код по постановке от аналитиков, а затем при помощи вспомогательных библиотек и аннотаций на основе готового кода генерируем спецификацию.

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

  3. API‑first: когда спецификацию (в нашем случае OpenAPI) пишут на основе аналитики до начала разработки.

В таблице ниже описана практика и мои мысли о преимуществах и недостатках каждого подхода


Code-first

Hybrid

API-first

Параллельная разработка

Нет: ожидание полной реализации.

Да: реализуют «заглушку», на основе которой генерируют спецификацию.

Да: спецификацию могут сразу использовать все потребители.

Написание аналитиками

Нет

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

Да

Качество API

Низкое: разработчики редко пишут корректное описание полей и приводят примеры.

Также генерирование спецификации из кода часто приводит к большому количеству мусора в спецификации: дискриминаторы, ненужные типы и т. д.

Среднее: при условии контроля качества на первом этапе.

Высокое: мы можем описать спецификацию идеально — с примерами и описаниями от аналитиков.

Удобство использования разработчиком

Высокая: сразу пишем код и тут же получаем спецификацию.

Средняя.

Недостатки: необходимость написания кода в библиотеках

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

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

Низкая: при ручном преобразовании в код.

Подходит для

Прототипы, мелкие проекты, API исключительно для взаимодействия с фронтендом.

Описание API для внутреннего взаимодействия бэкенд-фронтенд и взаимодействия микросервисов в рамках одной системы.

Крупные проекты, публичные API.

Реальный опыт использования в проекте

Показал себя в большом проекте абсолютно нерабочим:

— Разработчики мучались с аннотациями, и всё равно спецификации выходили кривыми и нечитаемыми.

— Написание кода по аналитике в «свободной форме» также приводило к разночтениям и постоянным переделкам.

— Изменения в коде не доносили до аналитиков.

— Вытащить и опубликовать спецификацию было проблемой: надо брать её с какого‑то стенда, куда‑то публиковать, следить за актуальностью, где‑то держать реестр микросервисов и пути до спецификаций в каждом из них.

Хорошо показал себя для работы «внутри» продукта, но оказывается неудобным при проектировании и согласовании публичных API.

Аналитики не могли легко вносить изменения и оперативно их согласовывать: постоянно ходили к разработчикам и мучали их.

История с библиотеками тоже не очень удобна: нужно постоянно их переопубликовывать в Nexus.

Остаётся проблема, когда разработчик должен перевести аналитику в код.

Хорошо показываетсебя для описания любых API — и внутренних, и внешних — при соблюдении следующих условий:

— Аналитики и разработчики хорошо понимают стандарт описания API (в нашем случае OpenAPI).

— Для генерации кода из спецификации используются специализированные инструменты, это позволяет избежать разночтений при ручном написании кода.

— Позволяет поймать огромное количество ошибок и в аналитике, и в её переносе в код.

Недостатки: аналитики должны хорошо знать стандарт OpenAPI.

В проекте мы пробовали и проходили все три подхода: Code‑first → Hybrid → API‑first. И в итоге остановились на API-first с обязательной генерацией кода (написание кода руками без кодогенерации запрещено вплоть до отказа на этапе code review).

Для генерации кода используем https://openapi‑generator.tech/ — открытый инструмент, поддерживающий все основные языки программирования и с отличной кастомизацией. Существует огромное количество аналогичных инструментов, пробуйте и выбирайте те, что вам нравятся — это вкусовщина.

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

Шаг три - есть спецификация, а как с ней жить?

Мы выбрали стандарт описания API, умеем на его основе получать работающий код, и этот код всегда актуален и соответствует описанию — мечта.

Что осталось?

  • Как версионировать API?

  • Как взаимодействовать со смежниками?

  • Как объединить все инструменты в один процесс, который помогает всей команде, а не мешает ей?

Для решения этих проблем мы задались вопросом хранения и версионирования — то есть вопросами управления жизненным циклом API.

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

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

У нас в организации уже существовал инструмент для управления жизненным циклом API, и мы решили поработать в нём. Какой это инструмент — не важно, так как на рынке есть достаточное количество аналогов (Postman Platform, Stoplight), выбирай любой или пиши свой.

Давайте сравним два варианта:

Git

Специализированное ПО

Версионирование

Да

Да × 2.

Почему × 2? Инструмент в рантайме создаёт отдельные «ветки» при поднятии версии и сбрасывает согласования для новой версии.

Совместная работа

Да

Да

Визуальные инструменты

Нет

Да

Управление жизненным циклом

Нет

Да

Генерация заглушек

Нет

Да

Преимущества

— Всем известный инструмент.

— Хорошо реализован механизм отслеживания изменения.

— Просто использовать код и так храним в Git. Добавить ещё один репозиторий — не проблема.

— Закрывает вопрос в нарезке прав на конкретные спецификации (не требуется размещать каждую спецификацию в отдельном репозитории для выдачи прав).

— Инструменты разработки и проверки спецификаций через веб‑интерфейс.

— Простой workflow для согласования спецификаций: черновик, согласование, зафиксировано.

— Публикация спецификации в удобном для просмотра виде (SwaggerUI).

— Просмотр кто, когда и что менял, откат изменений при необходимости прямо в интерфейсе.

— Часто есть возможность интеграции с инструментами создания mock‑сервисов, что тоже ускоряет разработку.

Недостатки

— Требуются специальные инструменты для разработки и отображения спецификаций.

— Требуется на уровне каких‑то внутренних соглашений решать вопросы фиксации версий, нарезки и объединения веток, так как в процессе участвуют не только разработчики, но и аналитики, архитекторы.

Нужно внедрять и поддерживать инструмент.

Итоги

— Неплохой способ надёжного хранения и разработки спецификаций, но нам мало.

— Хотим удобства и фишек для командной работы

— Использование Git — нормально и удобно в малых и средних проектах.

— Очень удобно и круто.

— Стоит заниматься в больших организациях и больших и долгосрочных проектах, когда мы вкладываемся в процессы и команду.

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

Важно, что внедрение стандарта OpenAPI не прошло просто и легко, особенно с учётом того, что у нас в команд нет«чистых» системных аналитиков. Что мы сделали для их легкого старта:

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

  • Затем несколько спецификаций писали сначала разработчики и отдавали на косметическую доработку аналитикам. Ещё несколько спецификаций аналитики писали сами, в «сыром» виде отдавали на проверку разработчикам, и вместе допиливали реализацию до пригодного к кодогенерации вида.

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

  • Сформировалась подборку «Типовых интеграций» — спецификаций под все типы полей и взаимодействий. Теперь спецификацию можно скопировать и адаптировать под свои нужды

  • Использование специализированного ПО помогает аналитикам в работе:

    • автоматическое «ветвление» при поднятии версии;

    • визуальный редактор;

    • валидация;

    • встроенный ассистент на основе LLM.

Разработчикам же в итоге передают ссылку на финальную версию, которую они могут прописать в инструмент кодогенерации, посмотреть, что получилось на выходе, и при необходимости дать обратную связь или подправить нестыковки самостоятельно.

Поле добавили — интеграцию сломали. Версионирование

Для того, чтобы гарантированно избежать цирка с несовместимостью версий API, мы используем semver-нотацию и фиксируем версию в спецификации в поле version:

  • major — кардинальные изменения в API, которые ломают существующую интеграцию;

  • minor — изменения, не ломающие существующие взаимодействия;

  • patch — мелкие фиксы, возникающие при разработке, класса «уточнить пример», «поправить имя поля в момент согласования спецификации» (до реализации) и т. д.

Что же касается внесения breaking changes: используем подход с версионированием имён топиков для Kafka и версиями в path для HTTP. Это просто, наглядно и легко тестируется.

Как пример для Kafka: первая версия — просто CreateProjectRq, вторая версия уже CreateAutorefitProjectRq.v2. Почему в первой версии нет v1? А не надо переусложнять, второй версии может и не быть.

Отключаем старые endpoint после перехода всех потребителей на новые с задержкой в +1 релиз — наш и потребителей (на случай, если кому‑то потребуется откатить с прода кривой релиз). Тоже ничего сложного

Итоги

Сейчас в моём текущем проекте процесс работы с API у нас выглядит примерно так:

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

Такой подход сильно экономит нам ресурсы: нервы и силы команде и мне лично. По моей оценке, высвобождается порядка 5 человеко‑дней на стандартную agile‑команду. Причём экономим на рутинных активностях и исправлении ошибок. Интеграционные ошибки ушли как класс, если обе команды используют кодогенерацию.

  • Сократили время согласования спецификаций между командами и раннее выявление нестыковок в реализации — одна правда для всех, и это спецификация. Как пример: на спецификацию на одно интеграционное взаимодействие, по моей оценке, уходит на 1 день меньше трудозатрат и на 3 дня сокращаются календарные сроки

  • Получили актуальную аналитику, которая на полностью соответствует реализации — смотрим в OpenAPI‑спецификацию и верим только ей, так как код генерируем из неё же. Что важно, появились нормальные примеры от аналитики, а не тот трэш, что любят писать разработчики

  • Уменьшили трудозатраты разработчиков на написание кода: теперь генерируем его, а не пишем руками. С учётом пересогласований и повторений генерации экономим порядка 2 дней разработчика в спринт. Не так давно мы в процессе разработки пересогласовывали и дописывали спецификацию три раза, причём разработчики не нервничали, а просто вставляли новую ссылку на спецификацию и выполняли mapping изменившихся полей, видя, что изменилось непосредственно в изменившемся коде.

  • Убрали ошибки, когда разработчики фронтенда и бэкенда или клиента и сервера реализуют спецификацию по‑разному — опять же, благодаря генерации кода. Пример: в первом же проекте с использованием спецификаций поймали ошибку смежников ещё на этапе их разработки, когда реализация не соответствовала спецификации (неверный тип: string, а не long), прогнав присланный пример через валидатор. В случае ручных проверок увидели бы ошибку на интеграционном стенде — это съело бы время но доработку и повторное тестирование.

  • Осчастливили команду QA, которая очень хотели описанное API для написания автотестов заранее и по понятным правилам. Теперь QA могу начинать разработку одновременно с разработчиками и могут сверять реализацию со спецификациями, а не только с текстом постановки.

Заключение и напутствие

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

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