Как строить современный процесс работы с API и чем отличаются подходы CodeFirst и ContractFirst? Рассмотрим преимущества и особенности применения обоих, научимся увеличивать продуктивность команды и превентивно решать блокеры.
Привет, Хабр! Меня зовут Иван Поддубный, я CTO в «Вебпрактик». Эта статья написана по мотивам моего доклада для Techlead Conf.

Проблемы при работе с контрактами
При работе с контрактами возникает целый ряд проблем и задач:
Неактуальная документация.
Недостаточно полное описание контрактов.
Несоответствие реализации контрактам.
Потери эффективности на согласовании контрактов.
Поддержка качества контрактов — соблюдение внутренних стандартов и лучших практик на рынке.
Блокеры процессов из-за задержки поставки контракта. Например, пока бэкендер разрабатывает свой API-контракт, фронтендеры простаивают.
Блокеры процессов из-за зависимости клиентов и QA от тестовых контуров. Например, когда тестовый контур нестабилен, клиенты, фронтенд, QA-тестировщики могут блокироваться этим процессом.
Проблемы тестирования контрактов.
Думаю, многие в своих проектах сталкивались хотя бы с частью этих проблем. В то время как типовая задача техлида — построить эффективный техпроцесс так, чтобы снять большую часть этих болей.
Типовая задача техлида: построить эффективный техпроцесс!
Аспекты эффективного техпроцесса в рамках работы с контрактами
Чтобы построить эффективный техпроцесс, нужно:
Минимизировать блокеры.
Уменьшить человеческий фактор.
Максимально всё автоматизировать.
Современный контракт ДОЛЖЕН БЫТЬ машиночитаемым
Контракт — это формальное соглашение или спецификация, определяющие, как клиент и сервер должны взаимодействовать между собой.

Современные контракты между сервисами должны быть машиночитаемыми, то есть описанными в таких форматах, как YAML, OpenAPI или AsyncAPI. Это позволяет системам автоматически понимать структуру API, облегчает интеграцию и тестирование.
Использование устаревших методов, таких как отправка ссылок на API-эндпоинты в Slack, Telegram или других мессенджерах, а также ведение не стандартизированных таблиц в Confluence, создаёт хаос и усложняет поддержку документации. Эти подходы постепенно уходят в прошлое, уступая место формализованным декларативным описаниям API.
Недавно на конференции меня неприятно удивило, что половина аналитиков до сих пор предпочитает писать таблички в Confluence вместо того, чтобы использовать существующий стандарт рынка.
Напомню как выглядит машиночитаемый формат. Пример OpenAPI:

На левом изображении представлен OpenAPI-конфиг, в котором декларативно описаны API-эндпоинты и их параметры. Справа — интерфейс Swagger UI, где каждый эндпоинт подробно задокументирован: можно просмотреть параметры, ознакомиться с документацией и протестировать запросы прямо в интерфейсе.
Применение OpenAPI и AsyncAPI даёт множество преимуществ:
Читаемость для машин и людей — разработчикам и автоматизированным инструментам легко работать с API.
Автоматизация — генерация кода, тестов и документации на основе единого описания.
Единый стандарт — команды говорят на одном языке, сокращая риск ошибок и недопонимания.
Упрощение интеграции — сторонним системам легко взаимодействовать с API.
А ещё сейчас с ним очень хорошо помогает AI
Пора оставить в прошлом хаотичные ссылки и вручную написанные таблицы. Используйте современные стандарты — это ускорит разработку и повысит её качество.
Дисклеймер
Рассматриваемые далее методы:
Полностью применимы для REST API.
Частично совместимы с Async API (событийными API).
Ограниченно подходят для gRPC.
Подходы CodeFirst и ContractFirst: в чём разница
Давайте вспомним в чём основная суть различия:
CodeFirst |
ContractFirst |
Сначала пишем код |
Сначала пишем контракт |
На основе (бэкенд) кода |
На его основе имплементируем код. |
Разберём построение процессов с помощью этих подходов.
Допустим, у нас небольшая команда:
фронтендеры (N человек).
бэкендеры (N человек), которые взаимодействуют по API.
Аналитик в этой команде не пишет контракт, он пишет ТЗ или ЧТЗ, который затем спускает команде для реализации.
За контракт в этом случае у нас отвечает бэкендер.

CodeFirst
Начнём строить процесс с помощью Code First:

Бэкендер пишет код, из него генерирует контракт, к примеру, документацию для более удобного чтения, и передаёт фронтенд-разработчику.
В результате:
Как минимум, часть процесса автоматизирована.
Контракт максимально синхронизирован с реализацией.
Документация и контракт пишутся совместно, соответственно, будет меньше расхождений.
CodeFirst подход пытается проанализировать код и на его основе генерировать документацию. Но не всегда в коде достаточно деталей, чтобы понять, как должны строиться роуты. Для этого используются аннотации.

Выше пример Java-контроллера класса. Красным выделены аннотации, которые необходимо прописать, чтобы сгенерировалась более полная документация.
Тут важно сказать, что в разных языках программирования и фреймворках эти аннотации пишутся по-разному. Например, в стандартных Java фреймворках аннотации должны показывать, какой роут какому эндпойнту принадлежит. Это не удобно. Есть реализации, когда эту информацию можно взять из самого кода роутера, описанного на бэкенде, вместо аннотаций.
Можем ли запараллелить работу?
Чтобы фронтендер не зависел от завершения работы бэкенда, можно разделить задачи на два этапа и запараллелить их.

Сначала бэкендер создаёт минимально необходимый код, который генерирует контракт в машиночитаемом виде (например, OpenAPI/Swagger) и формирует yaml-конфиг. Этот контракт передаётся фронтендеру как можно раньше, позволяя ему начать работу, не дожидаясь полной реализации бэкенда.
Такой подход мы успешно применяем уже много лет. Бэкендер на первом этапе прописывает роутеры, контроллеры и примеры данных, а фронтендер сразу подключается к процессу. В результате команды работают параллельно, ускоряя разработку в рамках Code-First подхода.
Поскольку уже есть описанные контроллеры и роутеры, у нас фактически готов тестовый сервер. Это значит, что фронтендеру можно передать не только документацию, но и рабочий API для интеграции.
Фронтендер сможет сразу подключать роуты к своему приложению и тестировать взаимодействие с бэкендом, даже если основная логика ещё не реализована. Это позволяет ускорить процесс разработки и минимизировать задержки при интеграции.

Возникает логичный вопрос: можем ли мы сразу запараллелить работу фронтендера и бэкендера, чтобы не было ожидания? Почему фронтендер должен тратить время впустую или подключатся с задержкой?
На самом деле — можем. В начале проекта или новой задачи у фронтендера всегда есть работа, которую можно выполнить независимо от готовности контракта. Например:
Настройка локального окружения — линтеры, тестовые стенды, конфигурация проекта.
Разработка компонентов и вёрстки, даже если API ещё не готов.
Подготовка заглушек для API-запросов, чтобы можно было тестировать интерфейс до реальной интеграции.
Так фронтендер не теряет время и начинает работу параллельно с бэкендом, а затем быстро интегрируется в работу над контрактом.

Получаем ещё два преимущества подхода Code First:
Позволяет работать параллельно или с небольшой задержкой.
Раннее появление тестового сервера снижает время на отладку.
Если фронтендер с самого начала взаимодействует с вашим сервером, это экономит время и снижает вероятность проблем на этапе интеграции. Вместо того чтобы отдельно разрабатывать фронтенд и бэкенд, а затем пытаться их «склеить», сталкиваясь с несовпадением данных, API и бизнес-логики, лучше наладить взаимодействие в самом начале. Вы получаете возможность сразу проверять контракт — фронтенд получает тестовый сервер с API и может заранее убедиться, что всё работает корректно. Нет ситуации, когда к концу разработки приходится исправлять множество несовместимостей, не нужно откладывать финальную интеграцию на последний момент.
Результат: быстрое внедрение, меньше багов, более стабильный продукт.
Но давайте подумаем, как техлиды могут усовершенствовать этот процесс.
Прокачиваем автоматизацию фронтенда
Посмотрим на фронтенд и попробуем проанализировать, что можно автоматизировать лучше. Для этого разобьем работу фронтендера на составные части:
Вёрстка компонентов.
Написание и поддержка типов + валидация.
(Почти все современные фронтенды пишут на TypeScript)Написание и поддержка слоя взаимодействия с бэкендом (client).
Написание остальной логики и тестов.
Для автоматизации можно сгенерировать все типы на основе моделей, которые есть в контрактах. Можно написать клиентский слой, используя машиночитаемый контракт. А чтобы прокачать автоматизацию фронтенда, сгенерировать из контракта типы данных. На самом деле поддержка своей системы типов на фронтенд-клиенте — это объёмная часть работы, если её не автоматизировать.
А ещё генерация клиента из контракта упростит кодовую базу. Так мы серьёзно уменьшим количество ошибок на уровне статического анализа IDE, проверок консистентности CI/CD. В результате, сократим объём задач и ускорим работу наших фронтендер-разработчиков.
Выглядеть это может так:

В нашем процессе, как только появляется контракт API, фронтенд может автоматически сгенерировать все типы и клиент, что значительно ускоряет разработку и снижает количество ошибок.
Для этого есть отличные инструменты, и один из лучших — Kubb — мощная библиотека, которая генерирует типы и API-клиент на основе OpenAPI, автоматизирует работу с данными, сокращая рутинные задачи, упрощает поддержку кода, так как изменения в контракте сразу отражаются в типах фронтенда.
Использование таких инструментов позволяет фронтендерам работать быстрее, а код становится более предсказуемым и надёжным.

Стабильность и блокеры
Наверняка вы сталкивались с ситуацией, когда бэкенд-сервер (внутренний или внешний API) внезапно перестаёт отвечать. Это блокирует работу и мешает тестировать интеграцию. Но эту проблему можно решить с помощью mock-сервера, который имитирует API и позволяет продолжать разработку без ожидания реального бэкенда.
Вместо того чтобы писать моки вручную, мы генерируем mock-сервер на основе контракта. Это позволяет эмулировать реальные запросы и ответы API, параллелить работу фронтенда и бэкенда, тестировать интеграцию заранее без зависимости от серверов.
Генерируем mock-server на основе контракта
Чтобы моки были полезными, контракт должен быть полным:
Прописаны все эндпоинты.
Указаны примеры данных для корректного моделирования ответов.
Существуют инструменты, вроде Prism, Mockoon, WireMock, MSW, Microrocks, которые автоматически создают mock-сервер на основе OpenAPI/Swagger-документации. Это позволяет фронтенду работать автономно, даже если настоящий бэкенд ещё не готов или временно недоступен. Готовые мокированные API также ускоряют тестирование, так как можно проверять логику без реального бэкенда. Мы упрощаем CI/CD и делаем разработку более стабильной.

Итак мы уже достаточно прокачали CodeFirst подход.
Давайте посмотрим как можно строить современный техпроцесс на ContracFirst.
ContractFirst
Используя подход ContractFirst, мы или учим аналитика писать спеку в Open/Async API-формате, или пишем и согласовываем спецификацию руками разработчиков и техлида.
Если пишут разработчики, рекомендую обратить внимание на инструмент typespec (~5K звезд) от Microsoft. Он позволяет в более удобном, чем YAML, формате писать спецификацию OpenAPI.
Далее кладём спецификацию в отдельный репозиторий.
Все изменения производим через Merge Request с ревью.
Ревью может делать архитектор или коллега-разработчик. Например, бэкендер написал контракт, а фронтендер его ревьюит (кстати сейчас модно делать как раз наоборот, именно фронт проектирует клиентоцентричный контракт, а бекенд делает ревью).

Контракт поступает на вход бэкенда и на вход фронтенда. Теперь можем чуть иначе подходить к работе. Вытащили контракт, значит, можем подумать, как автоматизировать бэкенд.
Прокачиваем автоматизацию бэкенда
Проделываем то же самое упражнение. Выделяем куски работы бэкенда:
Написание контроллеров.
Написание валидаций.
Написание DTO.
Написание сервисов или useCase.
Написание моделей.
Написание миграций.
Написание бизнес-логики.
Написание контроллеров, валидаций, DTO — очень нудная часть работы, её можно автоматизировать. Даже сервисы, как минимум заглушки для них, можем сгенерировать автоматически.
Есть специальные библиотеки, которые генерируют даже модели и миграции, но это подойдет скорее только для простых проектов.
Если контракт изменили, то перезапишется только генерируемый код. Пользовательский код останется нетронутым. Хорошие генераторы кода организуют структуру проекта так, что обновления касаются только генерируемых файлов (например, API-клиентов, DTO, интерфейсов). Бэкендеру не нужно вручную править контроллеры — все необходимые изменения подтягиваются автоматически, а логика приложения остаётся в безопасности. В результате, нет риска случайно затереть важный код, генерируемые файлы всегда соответствуют актуальному контракту API. К тому же, требуется минимум ручной работы при изменениях в API.
Так мы улучшили техпроцесс:

Проверка качества контракта
В разработке API существуют готовые инструменты, включающие наборы best practices для автоматической валидации и статического анализа API.
Они умеют:
Проверять API на соответствие стандартам компании и best practices.
Анализировать код на ошибки и уязвимости.
Выявлять несоответствия между контрактом и реализацией.
Оптимизировать документацию и поддержку API.
Ниже – пара инструментов которые могут вам с этим помочь.

Spectral — линтер OpenAPI, проверяет API-контракты на соответствие best practices. Не ограничивается проверкой стилевых ошибок. Он способен анализировать более сложные логические аспекты API:
Проверка логики API. Можно задать правила, чтобы API соответствовало вашему справочнику сущностей. Это особенно полезно, если в компании есть единые стандарты именования и структуры API, которым должны следовать все разработчики.
Контроль над изменениями API. Никто не сможет добавить новую сущность без ревью — Spectral можно настроить так, чтобы любые изменения проходили согласование с архитектором или ответственным за API.
Автоматизация проверок. Инструмент можно интегрировать в CI/CD, чтобы все API-проверки выполнялись автоматически при каждом коммите.
Также можно посмотреть на RateMyApi. Он также доступен как сервис или как код. В качестве статистической проверки его можно подключить себе на сервер.
Существуют другие инструменты для валидации API, такие, как Swagger Validator, OpenAPI Generator, AsyncAPI Validator и другие.

Мы ставим линтер и уже этим серьёзно увеличиваем качество нашего контракта на выходе.
Уменьшаем время отладки
С помощью контрактов мы также можем уменьшить время отладки. Для этого у нас есть:
Инструментарий для контроля соблюдения контрактов.
Инструментарий для тестировщиков.
Инструментарий для визуального тестирования.
1. Проверка соблюдения контрактов
Для большинства фреймворков есть библиотеки, которые позволяют как на уровне сервера, так и на уровне клиента установить валидаторы.

В критически важных сервисах некорректный ответ API может привести к серьёзным сбоям. В некоторых сценариях лучше «умереть», чем отправить пользователю или другому сервису невалидный ответ, который может привести к каскадным ошибкам.
И если вам это критично вы как раз можете поставить себе на выход подобный валидатор ответа. Или в отдельных случаях внедрить его на клиент.
2. Контрактное тестирование
Также на основе контракта мы можем сгенерировать контрактные тесты.

Есть инструмент DREDD, поддерживающий интеграцию с Go, Node, PHP, Ruby, Python, Rust и даже Perl. Это позволяет генерировать автотесты контракта и гарантировать его консистентность с API.
3. Инструменты визуального тестирования
Здесь речь про пресловутый Swagger, но не им единым. Интерфейс Swagger UI уже десятилетия не менял свой дизайн.

Есть другие варианты. Мне, например, нравится Elements с его трёхколоночным дизайном.

Когда контракт очень большой и включает много эндпоинтов, Swagger может становиться неудобным. Огромный список API превращается в «простыню», и навигация по документации становится сложной.
Elements генерирует HTML-документацию на основе контракта. Каждая ручка получает отдельную страницу. А ещё у него удобный двухколоночный интерфейс: слева — описание API, справа — тестирование.
Если нет машиночитаемого контракта
Вне зависимости от выбранного подхода:
Сначала попробовать сгенерировать контракт из кода.
Для CodeFirst останется допилить аннотации, чтобы появился этот контракт.
Для ContractFirst всё чуть-чуть сложнее:
Допилить до чистого вида в исходном YAML и подключить генератор.
Подключить старые сервисы в новых сгенерированных маршрутах.
Если вы сгенерировали новый код на основе контракта, старый нужно отрефакторить. К примеру, если для новых контроллеров была создана отдельная папка, старые контроллеры также стоит постепенно или сразу перенести туда, чтобы сервисы запускались корректно.
Выводы: CodeFirst
CodeFirst может быть не менее контракто-центричен, чем ContractFirst.
CodeFirst немного отстаёт с появлением контракта в рамках процесса, но не критично, и нам под силу это нивелировать и полностью запараллелить процесс.
Качество контракта в CodeFirst может проседать по сравнению с ContractFirst, но это решается регламентом и линтингом. Линтеры можно прикрутить в рамках pipeline. Но всё равно качество будет ниже из-за отсутствия ревью на уровне кода командой, либо силами архитектора. Если вы отдаёте контракт на ревью фронтендеру, это менее формализованный процесс, а значит, в нём будет больше проблем.
С CodeFirst проще стартовать, его поддерживают по умолчанию все фреймворки.
Подход по-прежнему актуален, востребован, инструменты развиваются.
Выводы: ContractFirst
Простор для оптимизации процессов больше.
Подход больше подходит для больших команд и проектов, особенно когда есть выделенная роль аналитика, и он умеет писать в OpenAPI. Если он до сих пор не умеет, научите его, это поднимет эффективность всей команды.
Возможность обнаруживать проблемы на более ранних этапах, в том числе на самом старте. Когда вы только-только проектируете контракт, то сразу на уровне линтинга или ревью можете обнаружить ошибки.
Качество контракта по дефолту выше благодаря ревью.
Контракт можно поручать аналитикам (как постановщикам задачи) или фронтам (как будущим пользователям).
Сейчас часто практикуется, когда контракт проектируют именно фронтендеры и клиенты, как будущие пользователи, и затем отдают бэкендеру. Возможно, пока это непривычный, но удачный подход, поэтому на него всё чаще переходят.
Итого
Оба подхода имеют право на жизнь. Хотя некоторые утверждают, что ContractFirst — лучший, я считаю, что CodeFirst имеет свои преимущества. Его можно хорошо прокачать, автоматизировать. Мы во многих проектах до сих пор используем CodeFirst, хотя есть и ContractFirst.
Оба подхода могут стать отправной точкой для значительной оптимизации техпроцесса.
Чит лист точек роста техпроцесса
Это набор шагов, которые стоит сделать в проекте, если вы хотите прокачать свой техпроцесс:
Генерируем фронтенд из контракта.
Генерируем бэкенд из контракта (только для ContractFirst).
Генерируем тестовый сервер с тестовыми данными для бэкенд.
Генерируем mock-сервер для фронтенда, чтобы не зависеть от стенда бэкенда.
Генерируем mock сервер для тестов.
Генерируем тесты контракта.
Генерируем визуал для документации.
Подключаем линтер на контракт, чтобы повысить качество и контролировать его.
Подключаем проверки на выходе сервера или входе клиента, чтобы фиксировать отклонения контракта в тестовой или продуктовой среде.
Надеюсь, эта статья мотивирует вас улучшать техпроцессы в своей команде :)
P.S. Ссылка на презентацию кому нужно)
Если вам актуальны проблемы управления системами и людьми — ждём вас 5 июня на TechLead Conf X 2025. Поговорим о построении систем грейдирования и матриц компетенций. Рассмотрим различия между SkillBased- и PerfomanceReview-подходами. Разберём пирамиды менторства, систему аттестации новых сотрудников, вопросы мотивации и другие насущные проблемы.
SkyFoxZ
Неплохая статья, как раз только в субботу php-митап смотрел на смежную тему