Короткая суть. Некоторые команды до сих пор хранят «правду» об API в трёх несовместимых местах: в голове бэкендера, в страничке Confluence, которая устарела ещё прошлой весной, и в реальном JSON, который прилетает с прода. Эти три источника постоянно расходятся, и расплачиваются за это все — особенно клиентские разработчики. OpenAPI — это способ свести правду к одному файлу-контракту, понятному и человеку, и машине. В статье я разбираю, зачем это нужно, почему кодогенерация — далеко не главная причина, но при этом проговариваю, во что внедрение обойдётся бэкенду, и где у подхода реальные слабые места.
Ситуация
Я мобильный разработчик. Иногда при работе со структурами данных я имею дело с endpoint-ами, иду в вики, нахожу страницу с описанием — и не верю ей. Потому что по опыту знаю: страница описывает API таким, каким он был задуман полгода назад, а не таким, какой он сейчас. Дальше начинается знакомый ритуал: пишу в чат бэкендеру, он отвечает «глянь в сваггере», сваггер сгенерирован из аннотаций и показывает приблизительную картину, реальный ответ с сервера от неё отличается, потому что правило сериализации живёт в одном месте, а правило генерации описания — в другом. В итоге я делаю то, что делает большинство клиентских разработчиков: дёргаю endpoint «вживую», смотрю реальный JSON и верю только ему. То же самое бывает и при работе с внешними API (в том числе и солидных компаний).
Это и есть проблема источника правды. У нас не один источник, а несколько, и они конкурируют. Вики — это намерение. Код бэкенда — это реализация. Реальный трафик — это факт. И когда между ними возникает расхождение (а оно возникает всегда), цена ошибки ложится в первую очередь на потребителя API: на фронтенд, на iOS, на Android, на десктоп, на внешних интеграторов.
Эту боль на Habr описывали многие. Алексей, Java-разработчик ЮMoney, в статье «Как улучшить межсерверное взаимодействие и сэкономить время разработчика» формулирует её предельно прямо: «Swagger UI, который генерируется автоматом по метаданным классов, показывает очень примерное описание того, что у нас реально отдаётся из API. Поэтому фронты и мобильные разработчики не могут начать разработку, не вызвав endpoint на живую». Это не чья-то личная неаккуратность — это структурный изъян процесса, в котором правда не централизована.
Что такое OpenAPI (для тех, кто ещё не сталкивался)
OpenAPI — это открытый стандарт описания HTTP-API (прежде всего в REST-стиле) в машиночитаемом виде. Стоит сразу очертить границу: OpenAPI — не единственный язык контрактов и покрывает не любой протокол. Для строго типизированного межсервисного взаимодействия есть gRPC с Protobuf, для API с гибкими запросами — GraphQL со своей системой типов, для событийных и брокерных архитектур (Kafka, очереди сообщений) — родственный стандарт AsyncAPI. OpenAPI же занимает нишу REST/HTTP — и именно здесь, где живёт большинство клиент-серверных API мобильных продуктов, он стал де-факто стандартом. Раньше он назывался Swagger; в середине 2010-х спецификацию передали под управление OpenAPI Initiative (объединение под крылом Linux Foundation, учреждённое такими компаниями, как Google, IBM, Microsoft, PayPal, SmartBear и другими), и сегодня Swagger — это уже набор инструментов вокруг стандарта (Swagger UI, Swagger Editor и прочие), а сам формат называется OpenAPI Specification.
Технически это один файл (обычно YAML, реже JSON), в котором описано всё, что нужно знать о REST API: какие есть пути (endpoints), какие у них методы, какие параметры и тела запросов они принимают, какие коды ответов и какие схемы данных возвращают, как устроена аутентификация. Главное здесь не формат, а идея: один документ, который одинаково читают и человек, и инструменты.
Вот как выглядит описание одного простого endpoint — сервис, который возвращает задачу по идентификатору:
openapi: 3.1.0 info: title: TODO Service version: 1.0.0 paths: /tasks/{taskId}: get: operationId: getTask summary: Получить задачу по идентификатору parameters: - name: taskId in: path required: true schema: type: string responses: '200': description: Задача найдена content: application/json: schema: $ref: '#/components/schemas/Task' '404': description: Задача не найдена components: schemas: Task: type: object required: [id, title, done] properties: id: type: string title: type: string done: type: boolean
Это читается практически без подготовки: есть путь /tasks/{taskId}, он принимает идентификатор в пути, отдаёт либо 200 с объектом Task, либо 404. И — ключевой момент — этот же самый текст читают и человек, и машина: то, что вы сейчас разобрали глазами без подготовки, инструмент разбирает программно. Один файл, одна правда — и для людей, и для кода.
Переиспользование вместо копипасты: DRY в описании API
Прежде чем идти дальше, стоит отдельно показать свойство, которое в ручной документации почти недостижимо, а в OpenAPI даётся даром: переиспользование. Это, на мой взгляд, один из самых недооценённых аргументов, поэтому остановлюсь на нём подробно.
Схемы данных описываются один раз. В вики-документации одна и та же структура (скажем, объект пользователя или стандартная обёртка ответа) расползается по десяткам endpoints, и каждое описание живёт своей жизнью. Кто-то поправил поле в одном месте и забыл в пяти других — и вот документация уже противоречит сама себе. В OpenAPI структура описывается единожды в components/schemas, а везде дальше подставляется через $ref. Поправили в одном месте — изменилось везде. Рассогласование внутри самого контракта становится структурно невозможным.
Разные варианты ответов описываются кратко и переиспользуют общее. Endpoint редко отдаёт один-единственный вид ответа: есть 200, есть 400, 404, 409, и у ошибок обычно общая структура. В ручном описании это превращается в портянку, где обёртка ответа переписана для каждого кода заново. В OpenAPI все ошибочные ответы ссылаются на одну схему, а для «всех прочих кодов» есть default. Плюс наследование схем через allOf: общую часть нескольких родственных типов описывают один раз, а частные случаи её наследуют — тот самый DRY на уровне данных. Важная оговорка на будущее: allOf — это именно наследование, а не полиморфизм, и это спокойный, предсказуемый случай — он и описывается элегантно, и отображается генераторами в код без сюрпризов. С полиморфными union-типами (oneOf/anyOf) дело обстоит иначе, но о них речь пойдёт в разделе про недостатки.
Примеры тоже переиспользуются. Хорошо написанный пример ответа (examples) можно вынести в components и ссылаться на него отовсюду, где он уместен — из документации, из mock-сервера, из тестов. Один аккуратный пример работает сразу на нескольких потребителей, а не дублируется в каждом.
Посмотрите, как компактно это выглядит на практике:
yaml
paths: /tasks/{taskId}: get: operationId: getTask parameters: - $ref: '#/components/parameters/TaskId' # параметр описан один раз responses: '200': description: Задача найдена content: application/json: schema: $ref: '#/components/schemas/Task' examples: sample: $ref: '#/components/examples/TaskSample' # пример переиспользуется '404': $ref: '#/components/responses/NotFound' # общий ответ об ошибке default: $ref: '#/components/responses/Error' # всё прочее — одной строкой components: parameters: TaskId: name: taskId in: path required: true schema: type: string responses: NotFound: description: Не найдено content: application/json: schema: $ref: '#/components/schemas/Error' # та же схема ошибки Error: description: Ошибка content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Error: # обёртка ошибки описана ровно один раз type: object required: [code, message] properties: code: type: integer message: type: string Task: allOf: # наследование: общее описано один раз - $ref: '#/components/schemas/Entity' - type: object required: [title, done] properties: title: type: string done: type: boolean Entity: # базовые поля, общие для многих сущностей type: object required: [id] properties: id: type: string examples: TaskSample: value: id: "42" title: "Купить молоко" done: false
Здесь нет ни одного повторно описанного фрагмента: параметр, схема ошибки, обёртка ответа, базовые поля сущности и пример — каждый существует в единственном экземпляре, а endpoints на них ссылаются. В ручной документации ровно эти вещи копируются снова и снова. Именно в этом копировании и накапливается рассинхрон.
И ещё одно — на случай распространённого предубеждения, что «REST — это про старомодные «запрос-ответ», а всё современное мимо». Это не так. Современные версии OpenAPI умеют описывать и потоковые (streaming) ответы — те самые, по которым ответ приходит не одним куском, а постепенно, по мере генерации. Самый узнаваемый сегодня пример — общение с AI-чат-ботами, где текст «печатается» на экране по мере поступления токенов (как правило, через Server-Sent Events). Такие интерфейсы OpenAPI описывает наравне с обычными, а инструменты их поддерживают: например, официальный swift-openapi-generator умеет отдавать тело потокового ответа как AsyncSequence — то есть сгенерированный клиент выдаёт поток, который на стороне Swift обрабатывается родным for try await. Так что подход «сначала контракт» не запирает вас в парадигме прошлого десятилетия: он покрывает и то, что вы пишете прямо сейчас, интегрируя очередную LLM.
Идея единственного источника правды
Весь смысл подхода в одной фразе: спецификация перестаёт быть документацией и становится контрактом. С контрактом обращаются так же строго, как с кодом: он лежит в системе контроля версий, к нему пишут pull request'ы, его ревьюят, его версионируют по SemVer. Любое изменение API начинается с изменения спецификации, а не с правки кода и последующего «не забыть обновить вики».
В русскоязычных кейсах это формулируют по-разному — Contract-First, Design-First, Specification-First, Manifest-First, — но суть одна: сначала контракт, потом код. И тут важно сразу снять одно недоразумение. Существует и обратный подход — генерировать спецификацию из кода (code-first, через аннотации). Он кажется дешевле, но воспроизводит ровно ту проблему, с которой мы начали: спецификация остаётся вторичным, побочным продуктом, который отстаёт от реальности. Матвей Лихота из МТС Web Services в своём разборе формулирует это так: «документация, которую пишут руками отдельно от кода, устаревает уже в момент следующего коммита» — и именно поэтому его команда развернула процесс и сделала спецификацию первичной.
Когда контракт первичен, у него появляется свойство, которого нет ни у вики, ни у головы бэкендера: он одновременно является и человекочитаемой документацией, и входными данными для целого зоопарка инструментов. И вот тут начинается самое интересное.
Кодогенерация: полезно, но это не главное
Первое, о чём вспоминают при слове OpenAPI, — это кодогенерация: из спецификации можно сгенерировать клиентский и серверный код. Для клиентского разработчика это означает, что не нужно руками писать модели данных, парсинг, сетевой слой — всё это берётся из контракта и всегда ему соответствует.
Поскольку я пишу под Apple-платформы, мне ближе всего история со Swift. У Apple есть официальный swift-openapi-generator — это плагин для пакетного менеджера, который генерирует код прямо на этапе сборки. Это важная деталь: сгенерированный код не нужно коммитить в репозиторий, он всегда пересобирается из актуальной спецификации, а значит физически не может «разъехаться» с контрактом. Выглядит вызов сгенерированного клиента примерно так:
let client = Client( serverURL: URL(string: "https://api.example.com")!, transport: URLSessionTransport() ) let response = try await client.getTask(path: .init(taskId: "42")) switch response { case .ok(let ok): let task = try ok.body.json print(task.title) case .notFound: print("Задача не найдена") }
Обратите внимание: и метод getTask, и разбор ответа на случаи .ok / .notFound — это не то, что я писал руками, это сгенерировано из той самой YAML-спецификации выше. Компилятор теперь на моей стороне: если бэкенд изменит контракт, у меня просто перестанет собираться код в нужном месте, а не «упадёт в рантайме у части пользователей».
Кроме официального генератора в Swift-экосистеме есть и сторонние инструменты — например, проекты, оптимизированные под лёгкий сетевой клиент. Под Kotlin/Android и десктоп есть свои генераторы, а у Microsoft — отдельный кросс-языковой генератор клиентов Kiota, плюс есть инструменты, заточенные под .NET (NSwag). То есть из одного контракта команда может генерировать клиентов сразу под все платформы — ровно об этом рассказывают в кейсах ЮMoney, где из одной спецификации получают код и для iOS, и для Android.
Но вот что я хочу подчеркнуть, и это, на мой взгляд, главная мысль статьи: кодогенерация — это приятный бонус, а не причина внедрять OpenAPI. У сгенерированного кода есть своя цена (об этом ниже), и если бы всё сводилось только к ней, спорить о подходе было бы куда сложнее. Настоящая ценность контракта — в том, что он включает целую экосистему инструментов, которые работают, даже если вы не сгенерируете ни строчки кода.
Главный аргумент: выгоды, не связанные с кодогенерацией
Вот здесь, на мой взгляд, и лежит настоящий ответ на вопрос «зачем нам это». Перечислю по порядку.
Линтинг и единый стиль API. Контракт можно автоматически проверять линтером — самый известный инструмент здесь Spectral, есть и альтернативы вроде Redocly CLI и Vacuum. Линтер следит, чтобы все endpoints были в одном стиле, чтобы у операций были описания и идентификаторы, чтобы соблюдались внутренние конвенции и правила безопасности. Это превращает абстрактный «гайд по оформлению API» в исполняемое правило, которое срабатывает в CI, а не живёт в забытой вики-странице.
Mock-сервер из коробки. По спецификации можно поднять mock-сервер — например, через Prism. Это значит, что клиентский разработчик может начать работу до того, как бэкенд напишет хоть строчку реализации: mock отдаёт ответы, соответствующие контракту. Фронтенд и бэкенд работают параллельно, а не по очереди. Для меня как мобильного разработчика это, возможно, самый недооценённый пункт: я перестаю быть в хвосте очереди.
Контрактное тестирование. Тот же Prism умеет работать в режиме прокси: он пропускает реальный трафик через себя и сверяет и запросы, и ответы с контрактом, сообщая о любых расхождениях. Это и есть та самая защита от дрейфа: если реальный сервер начал отдавать не то, что обещано в спецификации, вы узнаёте об этом в тестах, а не от рассерженных пользователей. Для более глубокого тестирования есть инструменты, генерирующие тест-кейсы прямо из контракта (например, Schemathesis).
Документация, которая не врёт. Из спецификации генерируется красивая интерактивная документация — например, через Redoc/Redocly или Swagger UI. Но в отличие от вики, эта документация не может устареть: она порождается из того же контракта, который является источником правды. Расхождение между документацией и «правдой» становится структурно невозможным.
Обнаружение breaking changes. Это отдельный, очень важный для потребителя API пункт. Инструмент oasdiff сравнивает две версии спецификации и говорит, какие изменения ломают обратную совместимость, а какие безопасны. Его можно встроить в CI и блокировать pull request, который незаметно ломает клиентов. Для мобильной разработки, где старые версии приложения живут на устройствах пользователей месяцами, это критично: breaking change в API — это не абстракция, это упавшее приложение у человека, который не обновился.
Реверс-инжиниринг существующих API. А что, если спецификации нет, а API уже работает? Тоже не тупик. Есть инструменты, которые строят черновик OpenAPI из наблюдаемого трафика — расширения для браузера, которые слушают сетевые запросы (openapi-devtools), и утилиты, конвертирующие перехваченный трафик или коллекции Postman в спецификацию (mitmproxy2swagger, postman2openapi). Это позволяет «догнать» design-first даже на легаси.
Overlays — аккуратная модификация без правки оригинала. Отдельный стандарт OpenAPI Overlays позволяет накладывать на спецификацию изменения, не трогая исходник: добавить описания, скрыть внутренние endpoints перед публикацией наружу, подставить разные серверные URL для разных окружений. Это удобно, когда исходный контракт генерируется или поддерживается другой командой.
AI и MCP. Свежий пласт: по спецификации можно автоматически поднять MCP-сервер (Model Context Protocol) — например, через инструмент emcee, — и тогда AI-агент сможет ходить в ваш API как в набор инструментов. Контракт здесь снова работает как универсальный адаптер: то, что описано один раз, переиспользуется и людьми, и машинами, и языковыми моделями.
Редакторы и инструменты дизайна. Работать с контрактом локально стало удобно. Главный инструмент здесь — плагин OpenAPI (Swagger) Editor от 42Crunch для VS Code: он даёт практически то же, что онлайновый Swagger Editor, но локально — рендеренный preview документации (через Swagger UI или ReDoc), автодополнение (IntelliSense), навигацию по ссылкам, переход к определению и встроенный линтинг. Базовый редактор бесплатный и не требует регистрации; есть и более тяжёлые возможности аудита безопасности, часть из которых требует регистрации. Рядом стоит назвать ещё несколько инструментов: расширение Redocly OpenAPI для VS Code (валидация, навигация по $ref и preview документации — правда, для preview нужен ключ Redocly); Stoplight Studio — визуальный редактор спецификаций со встроенным mock-сервером на Prism; Insomnia от Kong — клиент с нативным OpenAPI-редактором и live-preview, локальным хранением и линтингом через Inso CLI; и Apidog — интегрированная платформа, которая объединяет дизайн, отладку, mock и тестирование API в одном месте. Здесь стоит отметить разделение на локальное и облачное: VS Code-плагины 42Crunch и Redocly, а также Stoplight Studio работают локально; Insomnia умеет хранить данные полностью локально (Local Vault); Apidog же — в основе своей облачная платформа, и это нужно учитывать командам с требованиями к хранению данных.
Обратите внимание: ни один из этих пунктов не требует генерации кода. Даже если ваша команда принципиально пишет весь сетевой слой руками, вы всё равно получаете линтинг, моки, контрактные тесты, честную документацию и защиту от breaking changes. Вот почему я считаю, что спор «генерировать код или нет» — вторичен по отношению к решению «иметь контракт или нет».
О недостатках
Апология в классическом смысле — это защита, которая не прячет неудобные факты. Вот они.
**Полиморфизм и дискриминаторы — это может быть больно (но реже, чем кажется). Сначала о масштабе. Подавляющее большинство типов в реальной спецификации — это простые плоские структуры, переиспользуемые через $ref; для них кодогенерация и весь остальной инструментарий работают безупречно. Наследование через allOf, которое мы только что хвалили за DRY, генераторы тоже, как правило, переваривают без сюрпризов. И сам allOf, и — тем более — полиморфизм с дискриминатором встречаются в живых контрактах заметно реже простых подтипов. Боль начинается именно с полиморфных «или-или» типов — когда объект может быть одним из нескольких вариантов (oneOf/anyOf с дискриминатором): вот здесь генераторы ведут себя по-разному и нередко выдают неуклюжий код. На Habr есть детальный разбор именно этой боли в контексте Java/Spring (статья «Генерация контрактов OpenApi или прикладной API first: oneOf, anyOf, allOf»): дискриминатор там описан как «именно та вещь, которая позволяет управлять генерацией кода при использовании полиморфизма», и он превращается в специальные Java-аннотации @JsonSubTypes. Вывод: дискриминатор работает, но требует аккуратности и понимания, как именно ваш генератор отображает полиморфизм в код. На клиенте история похожая. Это реальное ограничение — но, как я и сказал, скорее редкий угол, чем ежедневная преграда: для большинства endpoints вы его попросту не встретите.
Эргономика сгенерированного кода. Сгенерированный код почти всегда объёмнее и менее «родной», чем написанный руками. Имена в спецификации напрямую превращаются в имена в коде — «кривой нейминг в спеке означает кривой нейминг в коде». Некоторые генераторы спотыкаются на анонимных объектах и нестандартных конструкциях. Официальный swift-openapi-generator на Habr уже получил скептический обзор от iOS-команды Ozon: Андрей, разработчик приложения «Пункт Ozon», в статье «Готов ли Swift OpenAPI Generator для продуктивного кода?» жалуется на «невозможность повлиять на процесс генерации» и на очень долгую сборку зависимостей — у него пустой проект собирался около 105 секунд на MacBook Pro M1. С тех пор инструмент заметно повзрослел, но сам факт показателен: к выбору генератора нужно подходить трезво и проверять результат на своём реальном контракте, а не на учебном примере. А ещё за программистом всегда есть выбор опции: будет ли кодогенерация запускаться каждый раз при сборке или вручную.
Дисциплина поддержки. Контракт работает ровно настолько, насколько команда дисциплинирована. Если процесс позволяет менять код в обход спецификации, вы получите худшее из двух миров: и контракт, и реальность, и оба врут. В русскоязычных кейсах это описывают прямо: бывает, что аналитик нарисовал схему, разработчик по ходу дела поменял её «в коде», тестировщики завели полсотни дефектов по схеме аналитика — и выяснилось, что единого источника правды на самом деле нет. Формальное внедрение контракта без дисциплины проблему не решает, а маскирует.
Подход требует ролей и времени. Та же команда МТС отмечает: spec-first хорошо работает там, где есть аналитики и архитекторы, ответственные за спецификации, либо у разработчиков выделено время на их написание. Это не бесплатно. И это подводит нас к главному возражению.
«Это же лишняя работа для бэкенда»
Самое частое возражение, которое я слышу: «Тебе, клиентскому разработчику, хорошо — а нам, бэкендерам, теперь писать ещё и YAML-спецификации руками. Это лишняя работа».
Давайте разберём это возражение, не отмахиваясь. В нём есть две части, и их важно различать.
Первая часть — реальная. Да, у spec-first есть настоящая первоначальная стоимость. Кто-то должен сесть и написать контракт до начала кодирования. На новый сервис это часы, иногда дни работы пары человек. Для команды без выделенных аналитиков это означает, что нагрузка ложится на разработчиков. Такова цена, и притворяться, что её нет, — нечестно. Более того, при code-first спецификация формально «бесплатна» (генерируется из аннотаций), и для прототипов, MVP и API, единственный потребитель которого — вы сами, code-first действительно может быть разумнее. Я не утверждаю, что spec-first нужен всем и всегда.
Вторая часть — это сопротивление новому, замаскированное под аргумент о трудозатратах. И вот здесь стоит быть аккуратным. «Лишняя работа» в формулировке возражения часто означает не «суммарно больше работы», а «новая для меня работа, которой я раньше не делал и которую неохота осваивать». Это нормальная человеческая реакция — «привычка штука сильная», как правильно замечено в одном из упомянутых habr-кейсов. Но это не аргумент о трудозатратах, это аргумент о зоне комфорта, и его стоит называть своим именем.
Потому что если посчитать полную стоимость, картина меняется. Та работа, которая «исчезает» при code-first, на самом деле не исчезает — она размазывается и перекладывается на других и на потом. Матвей Лихота из МТС приводит конкретную цифру: на поддержание актуальности swagger-документации в десятке микросервисов у команды уходило «до 20% времени», а после перехода на Documentation-Driven Development «высвободилось около 20% времени разработчиков». Это не «лишняя работа бэкенда» — это уже понесённые потери, просто невидимые, потому что они размазаны по интеграционным багам, по переписке в чатах, по «дёрни endpoint вживую», по сломанным у пользователей клиентам после необъявленного breaking change.
Если перенести затраты в начало — написать контракт один раз и строго, — то дальше выгоду получают все: ошибки ловятся на ревью YAML за минуты, а не за день до релиза; клиентские команды стартуют параллельно на моках; breaking changes отлавливаются автоматически; документация перестаёт врать. То есть ответ на возражение звучит так: да, это реальная первоначальная стоимость для бэкенда, и её надо заложить в план; но значительная часть этой «новой работы» — это не дополнительные затраты, а перенесённые в начало и сделанные явными те затраты, которые команда и так несёт, только позже, дороже и чужими руками.
И ещё одна деталь, которая снимает напряжение: контракт не обязан писать в одиночку бэкендер. Его черновик отлично пишут аналитики и тестировщики, а клиентские разработчики наконец-то получают возможность влиять на API на этапе проектирования — приносить не JSON-файл «хочу вот так», а pull request к спецификации. Это разворачивает обычную динамику, где «бэкенд может поменять API в любой момент, а клиент обязан подстроиться», в сторону совместно согласованного контракта.
Как внедрять без риска: поэтапный план
Резко переходить на полный Contract-First всей компанией — смелая идея. Подход требует дисциплины, а дисциплина не вводится приказом. Гораздо надёжнее двигаться маленькими шагами, на каждом из которых вы получаете пользу, даже если остановитесь.
Начните с одного сервиса или даже одного домена. Сделайте его эталонным. Не пытайтесь описать сразу всё — это путь к выгоранию и брошенной затее.
Если API уже есть — не пишите контракт с нуля. Сгенерируйте черновик из реального трафика или из существующих Postman-коллекций, а потом доведите его руками.
Договоритесь о конвенциях и включите линтер. Прежде чем масштабировать, опишите, как у вас выглядит «хорошее» API, и закрепите это правилами Spectral в CI. Иначе каждый сервис будет в своём стиле.
Подключите моки и контрактные тесты до кодогенерации. Это самые быстрые победы с наименьшим риском: клиентские команды начинают работать параллельно, а дрейф отлавливается автоматически. Кодогенерацию можно отложить.
Поставьте в CI проверку breaking changes. oasdiff на pull request к спецификации — дёшево внедрить, дорого недооценить.
Кодогенерация, там где она оправдана, и обязательно с проверкой результата на вашем реальном контракте, а не на учебном примере.
Что должно остановить вас и заставить пересмотреть план: если спецификацию начинают править в обход (код разошёлся с контрактом и это никого не беспокоит) — значит, дисциплины нет, и прежде чем идти дальше, нужно чинить процесс, а не добавлять инструменты. Контракт без дисциплины — это просто ещё один врущий источник правды.
Вывод
OpenAPI — это не про то, чтобы «не писать код руками». Это про то, чтобы у команды был один источник правды о том, как устроен API, и чтобы этот источник был машиночитаемым, а значит — проверяемым, тестируемым и неспособным незаметно разойтись с реальностью. Кодогенерация — приятный бонус со своей ценой; настоящая выгода — в честной документации, моках, контрактных тестах, линтинге и автоматической ловле breaking changes.
У подхода есть цена и реальные слабые места: полиморфизм неудобен, сгенерированный код не всегда красив, а весь подход держится на дисциплине команды (впрочем как и альтернативные подходы). Возражение «это лишняя работа» отчасти справедливо — первоначальная стоимость действительно есть, и её надо закладывать; но вторая половина этой «работы» — это не новые затраты, а старые, перенесённые в начало и сделанные видимыми.
На мой взгляд, для команды, где у API больше одного потребителя, этот размен почти всегда выгоден. Вики врёт, прод молчит, а контракт, если относиться к нему серьёзно, говорит правду. Если дать ему такую возможность.
Balek
Разрешите, я оставлю ссылку на свою вчерашнюю статью: https://habr.com/ru/articles/1043948/
Идея в том, что не надо мучиться с натягиванием OpenAPI на свой проект, а ввести свой язык описания и кодогенераторы, согласованные с вашей архитектурой.
rlaconic Автор
Ни одна методология (как и инструменты, приёмы и прочие меры) не существует сама по себе в отрыве от людей, когда её применяют люди. Именно поэтому то, что подходит одной команде, может не сработать у другой — это вопрос квалификации, культуры и дисциплины (факторы связанные, но не тождественные). Но есть вещи, которые подойдут почти всем, — и готовый стандарт с контрактом во главе угла как раз из таких.
Я сознательно не стал затаскивать в статью инструменты более высокого уровня (тот же Microsoft TypeSpec): уместное применение таких абстракций обычно приходит уже после того, как набита рука на базовом контракте. И здесь я бы расставил приоритеты иначе, чем вы. Собственный язык описания и свои кодогенераторы — это вложение, которое нужно спроектировать, выверить и годами поддерживать своими силами. Думаю большему числу выгоднее взять готовый стандарт, уже отлаженный индустрией, хорошо показавший себя на практике, даже при том, что он охватывает меньшую область.
Поэтому совет «не мучиться с OpenAPI, а ввести свой язык» как общий рецепт, на мой взгляд, скорее переворачивает приоритеты: для большинства это не упрощение, а более тяжёлый путь.
Прочитал вашу статью — совпадений во взглядах действительно много, мы про один и тот же принцип. Но «мучиться с натягиванием OpenAPI» — это не про мой опыт. Наоборот: подход облегчал мне даже сольную разработку (даже когда спецификацию приходилось писать самому) — не говоря уже о мультиплатформенных командах. Потому что он даёт лучшее из двух миров: автоматическую генерацию кода из контракта, но без галлюцинаций ИИ. И всё это на основе однажды аккуратно организованного сетевого слоя, который становится привычнее и понятнее по мере использования из проекта в проект.