API-First: как мы внедряем привычный OpenAPI и «слегка подозрительный» AsyncAPI
Нейроны — природный пример API-centric дизайна
У нас тут была целая драма: мы решили полноценно перейти от бумажно-табличных стандартов описания API к нормальным человеческим OpenAPI и AsyncAPI. Если уже хорошо известный стандарт OpenAPI вызывал минимум вопросов и уже использовался некоторыми командами, то аналогичный стандарт для асинхронных взаимодействий стал основой этой самой драмы.
Итак, раньше в основном мы по старинке проектировали программный интерфейс и передавали разработчикам таблицы с описанием интеграционного взаимодействия. Т. е. два аналитика договаривались по некоему API на стыке их систем или сервисов, а затем каждый из них шёл в свою команду рассказывать и показывать, как он это понял. Заканчивалось всё тем, что иногда по нескольку недель и даже больше мы правили всякие весёлые баги, связанные с наименованием атрибутов и не только. Где uppercase, у кого lowercase, что с типами данных, в каком блоке должен быть атрибут, почему столько атрибутов с похожими названиями — всё это приводило к бесконечным циклам звонков, разглядывания спецификаций и реализаций. До драки не доходило, но пара случаев запомнилась.
Нам не хватало нормального единого формата, чтобы в первую очередь было детальное определение программного интерфейса, его обвязок, заголовков, координат на стендах (тестовых и промышленных) прямо в описании, и ключевое — чтобы всё это было машиночитаемым. Чтобы не выяснять отношений со смежными командами, а работать с единой для всех сторон спекой и генерировать код на её основании.
Разработчики поначалу очень не доверяли AsyncAPI на фоне OpenAPI (и для этого была очень конкретная причина), и именно в этом смысле началась ключевая часть драмы с новыми инженерными стандартами.
Подход API-First
Команда платформы розничного кредитного конвейера (у которой преимущественно были именно асинхронные взаимодействия через очереди сообщений) на раннем этапе решила обкатать инженерную практику API-First. Это в целом, как не сложно догадаться, когда сначала проектируются API, а потом уже сам сервис/система, но и не только — дело ещё и в форматах и правилах описания этих самых API.
Тут сразу хочется пофилософствовать в целом о проектировании и разработке, о различных подходах, например, о DDD, но это немного другая история. Я head профессии по системному анализу, и, естественно, мне пришлось коснуться практики API-First напрямую и участвовать во внедрении.
Итак, есть классическое проектирование систем, например, от модели данных, а есть проектирование от API — собственно API-First. Программные интерфейсы важны тем, что это точки соприкосновения между системами/сервисами, и они в итоге несут важную для окружения функцию, на которой обязательно нужно сфокусироваться вначале, особенно в условиях многообразия и сложности всевозможных интеграционных сервисов и команд разработки.
Многие инструменты проектирования API на рынке заточены под разработку систем с нуля. Т. е. мы проектируем и описываем все API нашей будущей IT-инфраструктуры, генерируем шаблонный код для всех необходимых (микро)сервисов и их методов, далее наполняем его логикой и смыслом.
У нас другая ситуация: основные сервисы уже есть в наличии, но нужно проектировать новые фичи для интеграций, иногда абсолютно новые взаимодействия, при этом ещё и по своему шаблону разработки. Нет необходимости разворачивать весь IT-ландшафт с нуля. Но важно научиться грамотно описывать API и обмениваться этим описанием.
Если у вас зоопарк систем и мегаполис команд — это становится весьма критичным.
API-First — это вариант мировоззрения проектировщика, будь то аналитик, разработчик или архитектор. Мы же из этого мировоззрения выкусываем техническую часть, связанную непосредственно со специфицированием API, и сейчас занимаемся её раскаткой. Созреем для большего — будем последовательно расширять, если это потребуется. Сейчас нам важно получать спецификацию API в машиночитаемом формате. Основная цель — уменьшить влияние человеческого фактора, когда недостаточно точно договорились, не поняли друг друга или не заметили, что именно написано в доке.
По сути, нам нужен был универсальный машиночитаемый язык для описания взаимодействий с правилами обмена этим описанием.
При этом с системами за пределами нашего IT-ландшафта (например, с «Госуслугами» или ПФР) мы можем интегрироваться, используя и немного другие форматы, более понятные для них, при этом соблюдая общий подход API-First. Вообще в России есть консервативная интеграционная традиция, в первую очередь — в отношении государственных сервисов. Какое-то время назад одним из самых популярных способов интеграции были протокол SOAP и обмен XML, многие от этого уже ушли, но такие взаимодействия всё ещё часто встречаются. Для этого есть WSDL и XSD-схемы, которые описывают веб-сервис и его API. По моему опыту это всегда что-то вроде: «Мы вам сейчас предоставим описание нашего API. Вот вам XSD — держите!»
Это работает, но, как я уже говорил, наш внутренний стандарт должен быть полнее и современнее.
OpenAPI и AsyncAPI
Кажется, что про OpenAPI слышал практически каждый. Собственно, при классификации взаимодействий, подлежащих специфицированию, с REST-интеграциями никаких вопросов не было. А вот AsyncAPI — это Terra incognita, потому что люди с ним практически не сталкивались.
Это новый стандарт. Инструменты вокруг AsyncAPI далеко не всех удовлетворяют. Когда разработчики бэка в команде платформы розничного кредитного конвейера пристально посмотрели на эту историю, они сразу начали воротить от неё нос:
— Да тут кодогенератор не так работает, а ещё и на Node.js написан. У нас никто не знает Node.js. Мы не хотим на этом писать, поэтому нам этот AsyncAPI не нужен. Давайте обойдёмся JSON-схемой.
Вроде всё вполне логично.
Кодогенератор — это та часть, которая по описанному API создаёт заготовку для исходника, снимая с разработчика ряд рутинных телодвижений. Задача у нас была — описать API, но неприязнь вызвал именно кодогенератор, потому что он не такой, каким мы бы хотели его видеть.
Первая часть работы с API — специфицирование. Я как поставщик (или Provider) сервиса описываю для внешних потребителей (или Consumers) свой замечательный программный интерфейс для подключения к нему. Я его определяю в как можно более полном объёме. Все остальные подстраиваются и запрашивают через меня модификации, если требуется. Я могу им отказать либо сказать, что готов поменять под них программный интерфейс. Потребители в моём API ничего самостоятельно не меняют. Они просто берут то, что я им предоставляю (с учётом их потребностей и моих возможностей), и далее используют в своих интересах. Работа в этой части с точки зрения аналитика заканчивается там, где мы спроектировали API, согласовали его с потребителями, положили его в репозиторий. Есть ещё задача описания бизнес-логики на стороне аналитиков потребителя, но это немного отдельная тема. А вот дальше включаются разработчики с обеих сторон. Они вытаскивают спеку, которую подготовил аналитик, и дальше на основании этого материала генерируют код, дополняя его потом бизнес-логикой.
Это самый сок, потому что разработчик ничего не переписывает. У него средства генерации работают таким образом, что всю эту структуру, которую заложил и согласовал аналитик на стороне поставщика, все особенности, типы, фиксированные списки значений плюс ещё координаты этого API для различных стендов от теста до прода и прочее — всё это хранится в одной спецификации. Разработчики с обеих сторон при генерации кода снимают с себя издержки переписывания с бумажки.
Естественно, что у нас есть свои шаблоны кода сервисов, но стандарты разных команд могут отличаться. У нас сейчас внутри Газпромбанка пока до конца не выстроены единые стандарты, но мы к этому очень стремимся. На данный момент идёт выравнивание относительно понимания, как должен выглядеть код сервиса, из каких частей он должен состоять, что допустимо, а что нет, и так далее.
В итоге мы пока не стали брать исходных кодогенераторов для AsyncAPI. Сейчас мы просто из целевого формата делаем старую добрую JSON-схему. С ней мы умеем работать в части кодогенерации, вся амуниция для этого в наличии.
Спецификации API также являются основой для контрактных тестов, которые дополнительно снимают много «головной боли» в части разработки интеграционных взаимодействий. Рассчитываю, что наши эксперты обязательно этим поделятся в отдельной статье.
Пока мы можем попилотироваться без средства генерации кода «из коробки» AsyncAPI. Когда будет нужно — напишем свой кодогенератор со всеми плюшками. Или найдём поклонников Node.js.
Наш гибкий подход в какой-то степени решил саму суть драмы, и команды начали тыкать в AsyncAPI палкой.
Организационно
После выбора стандартов и понимания задачи в целом мы сделали рекомендации в части плагинов и библиотек, которые позволяют выполнять конвертацию форматов с учётом нужд разработчиков.
Дальше составили чек-лист по различным видам интеграции, который позволяет командам определить, какие конкретно подход и стандарт выбирать при разных условиях. Описали процессы. Опубликовали всё это описание в Confluence. Потом провели несколько встреч.
Любая незнакомая технология вызывает понятное сопротивление команды, даже если явно определены преимущества. У некоторых аналитиков сопротивление было в форме: «А мы не понимаем, а нам нужен пример. Покажите». Очень полезно сначала изучать материалы самостоятельно, пробовать, а затем обсуждать технические детали. Когда человек приходит с конкретикой, и мы начинаем работать с точечными вопросами, начинается конструктив. Когда же он не хочет этого делать, то это тоже видно. Были люди, которые ринулись с удовольствием. А были те, кто не очень хотел изменений и инноваций. Это из серии, когда человек говорит: «Я не знаю английского, а вы мне скинули ссылку на статью на английском языке. Я ничего не могу сделать. У меня лапки. Всё».
Следующий уровень сопротивления был от разработчиков, которые не хотят переходить на формат AsyncAPI, потому что им неудобно. Но разработчики — люди умные, и потому они находят очень много аргументов. Как я говорил, мы упёрлись в кодогенерацию: «Хотите API First — нам вполне хватит JSON-схемы, не надо нам никакого Node.js и AsyncAPI». Но в итоге мы всё же нашли компромисс.
Вообще по мере того, как мы настойчиво объясняли причины, для чего мы это внедряем, становилось легче, появлялось больше сторонников. Иногда стоило попросить взглянуть на картину целиком по всему масштабу систем внутри банка и вокруг него.
Провели ещё несколько встреч. Дополнили стандарты и договорились, что пилотные участники, с которых мы начинаем раскатку, с определённого момента применяют этот подход.
Вдобавок для более жёсткого контроля соблюдения практики внедрили Quality Gate. То есть, по сути, команда не может выходить в релиз без правильно описанного API. DevOps-инженеры сделали проверки. Пока это простейшие проверки на наличие спецификации и её формат, думаю, что в перспективе мы столкнёмся с необходимостью развивать их и делать более точными и жёсткими.
Итог
Некоторые моменты стали понятнее. Ушла часть ошибок, хотя и остаётся ещё много вопросов. Но практика хорошая, раскатываем дальше за пределы тестовых команд. Через полгода можно будет ко мне вернуться, если хотите, и я расскажу, как дела.