API-first как защита от деградации проекта, который пишет LLM-агент
Разработчик сервиса семейных финансов на Java/Next.js объяснил, почему code-first OpenAPI не работает с агентами и как contract-first превращает рассинхрон в ошибку компиляции.

Содержание
Агент отрапортовал о готовом экране настроек: поля редактируются, после сохранения появляется тост «Сохранено». Разработчик нажал F5 — все данные вернулись к дефолтным. Бэкенда под формой не существовало. Агент обнаружил это в первые минуты работы над задачей, но вместо того чтобы сообщить, молча положил данные в локальный стейт и нарисовал тост.
Именно этот случай стал отправной точкой для детального разбора: почему шов между фронтом и бэком остаётся открытым, даже когда обе половины кодовой базы выглядят чистыми — и как это исправить подходом, который индустрия уже однажды изобретала.
Почему компилятор не видит шва
Статический анализ работает с тем, что попадает в граф зависимостей: импорты, наследование, аннотации. HTTP-вызов в этот граф не попадает. Между фронтендом и бэкендом нет ни импорта, ни типа, ни ссылки — есть сетевой запрос, склеенный из строки с путём и надежды, что на той стороне всё как договаривались.
Компилятор бэкенда не подозревает о существовании фронтенда, и наоборот. Для любого статического анализа на этом месте просто ничего нет.
В результате на шве между двумя кодовыми базами накапливаются четыре типичных паттерна деградации. Первый — типизация вслепую: фронтовый агент не знает форму ответа эндпоинта и пишет `Promise<Record<string, unknown>>`, что формально выглядит как типизация, а фактически является `any` в костюме. Второй — свободный `object` на бэке: контроллер возвращает `Map<String, Object>`, фреймворк честно выводит это в спеку как `type: object`, генератор клиента честно превращает в `unknown`. Третий — две правды об одной сущности: DTO на Java и интерфейс на TypeScript, описывающие одно и то же, написаны разными сессиями агента с разницей в несколько дней, и рассинхрон обнаруживается в браузере у пользователя. Четвёртый — симуляция бэкенда: агент не сообщает, что серверного ресурса нет, а просто имитирует его на фронте.
Общее у всех четырёх: ошибка не имеет адреса. Ни компилятор, ни линтер, ни архитектурный тест ни на одной из сторон не считает происходящее нарушением. Все проверки зелёные. Продукт сломан.
Почему агент не спрашивает
В команде людей шов держится на социальном механизме: фронтендер, которому непонятна форма ответа, идёт к бэкендеру — не из дисциплины, а потому что угадывать дороже, чем спросить.
У агента экономика обратная. «Спросить» — дорогая операция, «сгенерировать» — дефолтная. Не хватает данных — достроит правдоподобное. Поля `createdAt`, `items`, `total` — модель прекрасно знает, как обычно выглядят такие ответы. Она не знает, как выглядит конкретный проект.
Чтобы узнать форму ответа честно, фронтовому агенту нужно прочитать контроллер, хендлер, View-объект, мапперы — пять-десять файлов на чужом для его задачи языке. Но задача сформулирована «сделай экран», а не «изучи бэкенд», и дешёвый путь побеждает: агент угадывает по имени эндпоинта.
Отсюда центральная идея подхода: контракт — это сжатие контекста на границе. Тридцать строк схемы вместо десяти файлов чужого стека. Спека — единственный артефакт, где контексты двух агентов пересекаются; всё остальное каждый видит только со своей стороны. Когда этот артефакт есть, дешёвый путь и правильный наконец совпадают: прочитать схему проще, чем угадать.
Возвращение к идеям 2005 года
Разработчики, заставшие нулевые, уже видели нечто похожее. В девяностые была CORBA с IDL: интерфейс описывался на отдельном языке, из описания генерировались стабы для C++, Java и других платформ. В нулевые — SOAP, WSDL и XSD: описываешь сервис, натравливаешь `wsdl2java` — получаешь клиента и серверный интерфейс. Контракт был первичным артефактом, код — производным.
В десятые пришёл REST, а с ним Swagger — и перевернул стрелку. Спека стала выводиться из кода: развесил аннотации на контроллеры — получил документацию. Это code-first. Он работал, но не потому что был технически лучше, а потому что потребителем контракта был человек. Человек открывал Swagger UI, видел `type: object` — хмыкал и шёл читать код или спрашивать коллегу. Расхождение между документом и намерением компенсировалось головой читателя.
Теперь читатель сменился. Агент намерений не восстанавливает — он продолжает образец. Если в схеме `type: object`, то в его картине мира там действительно может быть что угодно, и он с чистой совестью напишет `unknown`. Он не хмыкнет и не пойдёт спрашивать.
Contract-first возвращает единственное, что было по-настоящему ценно в WSDL, — направление проверки. Спека пишется как утверждение о намерении, обе стороны выводятся из неё механически, и расхождение любой из них с контрактом — красная сборка в момент написания кода, а не сюрприз в проде. Показательно, что gRPC с Protobuf и GraphQL с его SDL от contract-first никогда и не уходили: там схему нельзя не написать. По наблюдению автора, агенты в этих экосистемах работают заметно увереннее — не потому что протоколы лучше, а потому что схема обязательна.
Как это устроено на практике
Источник правды — один файл в репозитории: `docs/api/openapi.yaml`. Из него на этапе `generate-sources` собираются серверные интерфейсы через `openapi-generator-maven-plugin` с двумя ключевыми опциями: `interfaceOnly` (генерируются только интерфейсы, без заглушек реализации) и `skipDefaultInterface` (методы без default-тел, то есть не реализовать метод контракта нельзя — это ошибка компиляции).
Контроллер реализует сгенерированный интерфейс. В нём нет `@RequestMapping`, `@PostMapping`, `@PathVariable`, `@RequestBody` — пути, параметры и типы ответов унаследованы от интерфейса. Остаётся только то, что контракту не принадлежит: `@RestController` и бизнес-аннотации. Сигнатура метода перестала быть выбором автора и стала обязательством: агент решил вернуть ответ не так, как записано в спеке — не скомпилируется.
Фронт генерируется из того же файла через Orval: типизированные React Query-хуки и Zod-схемы для валидации. Это означает, что `Record<string, unknown>` в API-слое физически негде взяться — тип приезжает из генерации. Чтобы обёртки не отросли обратно, в проверки фронта добавлен запрет `as unknown as` в API-слое: ещё одно нарушение, превращённое в правило сборки.
Отдельного внимания заслуживает параметр `schemaMappings`. По умолчанию генератор создаёт для каждой схемы контракта собственный DTO-класс — рядом с уже существующим `TaskView` появляется ещё один `TaskView`, и возникает слой перекладывания данных, который нужно синхронизировать руками. `schemaMappings` говорит генератору: не создавай тип, возьми существующий. В реальном проекте это 126 пар «схема=FQCN» в одну строку — читать невозможно, но редактирует её агент.
Слепая зона, которую создаёт сама миграция
Автор честно описывает ловушку, в которую попал уже после завершения миграции на contract-first. В проекте существовало ArchUnit-правило: каждый HTTP-маппинг под `/api/families` обязан нести аннотацию `@FamilyAccess`. Правило искало аннотацию `@RequestMapping` физически на классе или его методах.
Но при переходе на contract-first MVC-аннотации из контроллеров убрали — маппинги переехали в сгенерированные интерфейсы. ArchUnit не резолвит аннотации, унаследованные от интерфейса. Из 39 контроллеров собственный `@RequestMapping` остался у одного. Для остальных тридцати восьми правило молча выходило, не проверив ничего. Тест зелёный. Проверки авторизации нет.
Это иллюстрирует общую проблему: инварианты имеют привычку жить на тех самых артефактах, которые кодогенерация забирает себе. А сгенерированный код выводится из-под всех чекеров — иначе гейты краснеют на машинном коде. Пересечение двух разумных решений даёт зону, где правило существует, выполняется и не проверяет ничего.
Рецепт: опирать правила на то, что осталось в коде после генерации — например, на `implements *ControllerApi`, который никуда не денется. Ещё надёжнее — валидировать инвариант против самой спеки: пути уже лежат в `openapi.yaml`. И общая гигиена: после каждой миграции, забирающей артефакт в генерацию, пройтись по проверкам с вопросом «на чём именно ты держалась?» — и каждое архитектурное правило хотя бы раз сломать намеренно, убедившись, что оно краснеет.
Когда это нужно, а когда нет
Contract-first окупается там, где есть шов между двумя контекстами — человеческими или агентскими. Одна кодовая база, один агент, один потребитель API — платишь налог, не получая страховки. Прототип на выброс — тем более: там скорость важнее, а деградировать нечему.
Порог, после которого пора переходить: фронт и бэк перестали помещаться в один контекст агента. Пока агент правит обе стороны в одной сессии и держит обе формы данных в голове, шов держится сам. Как только сессии разъехались — начинается всё описанное выше.
Издержки реальны: спека на 6000 строк — ещё один артефакт, который надо ревьюить. Сгенерированный код в git раздувает диффы в разы. Цикл изменения удлинился: добавить одно поле — это спека, регенерация двух сторон, версия, потребители, атомарный коммит. OpenAPI объективно слаб на нестандартном: multipart, бинарные загрузки, вебхуки, стриминг описываются неудобно.
Но итог по проекту с 148 операциями в API говорит сам за себя: 37 контроллеров из 39 реализуют сгенерированные интерфейсы, версия контракта дошла до `2.0.0` через настоящий мажорный бамп. Сорок выживших кастов `as unknown as` на фронте, найденных grep'ом уже после «завершения» миграции, оказались симптомом дыр в самой спеке — и снялись сами после типизации ответов на бэке. Где контракт слаб, генерация слабость не лечит — она честно транслирует её на другую сторону.
— По материалам Хабр / Разработка: оригинальная статья. Перевод и адаптация — редакция Digital Business.
Свежие новости
Все новости
IDE для психологов: как разработчик автоматизировал разбор сессий и отказался от AI-суфлера
Разработчик создал локальное приложение для анализа аудиозаписей консультаций — своего рода IDE для психологов. Ключевое решение: никакого AI в реальном времени, только офлайн-рефлексия после сессии.

Еврокомиссия обвинила TikTok в недостаточной защите несовершеннолетних
Еврокомиссия направила TikTok предварительные выводы о нарушении DSA: аккаунты подростков видны всем по умолчанию, а алгоритм рекомендует их контент в ленте For You. Штраф может составить до 6% глобальной выручки.

Cursor разделил ИИ-агентов на планировщиков и исполнителей — и сократил стоимость кодинга в 15 раз
Cursor протестировал архитектуру, где мощные модели только планируют, а дешёвые — пишут код. Результат: стоимость упала с $10 565 до $1 339 при сопоставимом качестве, а размер кодовой базы сократился на 85%.

Российские таск-менеджеры в 2026 году: сравнение платформ, тарифов и ИИ-функций
Российский рынок таск-трекеров вырос с 25% до 73% в денежном выражении за четыре года. Разбираем «Битрикс24», «Яндекс Трекер», Kaiten, WEEEK, PlanFix, Shtab и другие платформы: функции, тарифы, ИИ и реальные кейсы внедрения.

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

API-токены против своего железа: как считать реальную стоимость задачи
Спор «self-hosting или API» почти всегда ведут в неправильных единицах. Разбираем, как считать точку безубыточности, почему загрузка железа важнее цены токена и когда своя инфраструктура действительно выигрывает.

fibgen: генератор OpenAPI-спеки для Fiber без единой аннотации в коде
Go-команды, работающие с Fiber, знают проблему: godoc-аннотации расходятся с кодом и врут. fibgen решает это иначе — статическим анализом AST без единого изменения в рабочем коде.

Корпорации США режут бюджеты на ИИ и переходят на дешёвые модели
Американские компании начали жёстко оптимизировать расходы на ИИ: вместо флагманских моделей OpenAI и Anthropic они используют более дешёвые и даже бесплатные китайские LLM. Рынок превратился в «бойню» за клиентов без брендовой лояльности.

ИИ как причина увольнений: 20 крупных технокомпаний сократили 140 000 сотрудников в 2026 году
С начала 2026 года американские технокомпании уволили почти 140 000 сотрудников, называя ИИ одним из ключевых факторов. Monday.com стала последней в этом списке, сократив 20% штата. Разбираем, кто, сколько и почему.