Контракт описывает и успех, и сбой

  1. 01ЗапросСмысл и данные
  2. 02ГраницаПрава и ограничения
  3. 03ОтветРезультат или ошибка
  4. 04ПовторСовместимость и эффект
Протокол задаёт средства. Поведение системы задаёт согласованный контракт.

За годы ревью интеграций я не раз встречал один набор решений: POST для чтения и удаления, 200 OK для любой ошибки, описание полей в отдельной таблице и отсутствие правил совместимости.

Пример ниже композитный, но узнаваемый:

POST /api/client/get
POST /api/client/delete
POST /api/client/getStatus

Сам по себе POST не запрещён. Проблема возникает, когда контракт не объясняет семантику, возможность повтора, ошибки и жизненный цикл данных. Именно этому я уделял больше всего внимания на курсе по системному анализу.

API — это не синоним REST

ПодходМодель взаимодействияКонтрактГде полезенОсновная цена
HTTP resource APIРесурсы и семантика HTTPOpenAPI — распространённый выборПубличные и внутренние API, веб-интеграцииДисциплина совместимости и единых соглашений
SOAPXML-сообщения и операцииWSDL + XSDСуществующие enterprise- и WS-*-экосистемыСложность спецификаций и инструментов
GraphQLЗапрос к типизированному графуGraphQL schemaНесколько клиентских представлений одних данныхСтоимость резолверов, запросов и governance
JSON-RPCВызов метода с параметрамиДоговорённость или внешняя схемаКомпактные RPC-интерфейсы и внутренние утилитыМало встроенных правил эволюции и обнаружения
gRPCТипизированные RPC-вызовы.protoВнутренние сервисы, streaming, генерация клиентовБинарный протокол и зависимость от toolchain

REST — архитектурный стиль, а не формат данных. Многие production API корректнее называть HTTP API с JSON: они используют URI, методы и коды HTTP, но не реализуют весь набор ограничений REST. Это не дефект, если контракт честно описан.

Семантика HTTP без мифов

HTTP stateless в том смысле, что каждый запрос должен быть понятен сам по себе. Сервер при этом может хранить данные, сессии и состояние ресурсов; нельзя лишь полагаться на неявный контекст конкретного соединения.

Методы выражают намерение:

  • GET читает представление и имеет безопасную, идемпотентную семантику;
  • POST передаёт ресурсу данные для обработки — создание является частым, но не единственным сценарием;
  • PUT создаёт или заменяет состояние целевого ресурса и идемпотентен по предусмотренному эффекту;
  • PATCH применяет частичное изменение и не обязан быть идемпотентным;
  • DELETE удаляет связь с ресурсом и идемпотентен по предусмотренному эффекту.

Если процедура плохо ложится на ресурсную модель, не нужно маскировать её любой ценой. Например, POST /risk-assessments может означать создание результата оценки. Для явного RPC-вызова честнее рассмотреть JSON-RPC или gRPC, чем придумывать искусственный ресурс.

Коды ответа тоже являются частью контракта:

  • 200 OK — запрос успешно обработан;
  • 201 Created — создан новый ресурс, обычно с Location;
  • 204 No Content — успех без тела;
  • 400 Bad Request — запрос некорректен для обработки;
  • 401 Unauthorized — не предоставлены корректные данные аутентификации;
  • 403 Forbidden — сервер понял запрос, но отказывает в доступе;
  • 404 Not Found — целевой ресурс не найден или не раскрывается;
  • 409 Conflict — запрос конфликтует с текущим состоянием;
  • 500 Internal Server Error — сервер столкнулся с неожиданным условием.

Возвращать 200 при сбое обработки, который контракт описывает как HTTP-ошибку, — плохая практика: gateway, метрики и клиенты теряют стандартный сигнал. Но отрицательный бизнес-результат не обязательно ошибка запроса. Например, успешно выполненная оценка с результатом «не одобрено» может вернуть 200: решение находится в теле ответа, а код сообщает об успешной обработке. Это различие нужно описать в контракте. У GraphQL другая модель частичного результата, где ответ может содержать и data, и errors; её нельзя механически переносить на любой JSON API.

SOAP: messaging framework, а не готовая транзакция

SOAP задаёт XML-envelope и модель обработки сообщений. WSDL и XSD дают зрелое описание операций и типов, поэтому технология остаётся полезной в системах, где такой контракт уже является стандартом взаимодействия.

WS-Security, WS-ReliableMessaging и транзакционные расширения — отдельные спецификации и профили. Наличие SOAP не включает шифрование, гарантированную доставку или распределённую транзакцию автоматически. Эти свойства зависят от выбранных расширений, binding, реализации и общей архитектуры.

Выбирать SOAP стоит ради совместимости с существующим ландшафтом и конкретных требований к стеку WS-*, а не по формуле «нужна надёжность — берём SOAP».

GraphQL: гибкость запроса требует governance

GraphQL публикует типизированную схему, а клиент выбирает нужные поля. Это удобно, когда веб, мобильное приложение и внутренний интерфейс используют разные представления одних сущностей.

Сложность переезжает на сервер:

  • резолверы могут породить N+1-запросы;
  • глубину и стоимость запросов нужно ограничивать;
  • авторизация должна работать на уровне бизнес-объектов и полей;
  • изменение схемы требует deprecation-политики;
  • производительность нельзя оценить только числом HTTP-вызовов.

GraphQL чаще передают по HTTP в JSON, но спецификация языка не привязана к одному транспорту. Query может идти через GET, что позволяет использовать HTTP-кеширование; также применяются persisted documents. Поэтому «GraphQL невозможно кешировать» — слишком сильное утверждение.

gRPC: контракт и streaming, а не магическая latency

gRPC описывает сервисы и сообщения в .proto, генерирует клиентский и серверный код и поддерживает unary, client-streaming, server-streaming и bidirectional-streaming вызовы. Основной стек использует HTTP/2, а Protobuf является стандартным форматом, хотя сам framework допускает и другие content types.

Он удобен для внутренних типизированных интерфейсов, но требует дисциплины вокруг deadlines, cancellation, retries и совместимости схем. Бинарный payload хуже читается глазами, зато есть специализированные инструменты вроде grpcurl и server reflection.

Нельзя обещать, что gRPC всегда быстрее JSON/HTTP в заданное число раз. Это проверяется на реальном payload, библиотеках, сети и профиле вызовов. Для внешнего браузерного API также нужно учитывать gRPC-Web или gateway и возможности инфраструктуры между клиентом и сервером.

Как я выбираю подход

Вместо таблицы «лучший протокол» я отвечаю на вопросы:

  1. Кто потребитель и какие технологии он реально поддерживает?
  2. Взаимодействие естественно описывается ресурсом, процедурой или запросом к графу?
  3. Нужны ли streaming и двунаправленный обмен?
  4. Как будет эволюционировать контракт и сколько версий поддерживается?
  5. Какие прокси, gateways и средства диагностики стоят на пути?
  6. Каковы требования к latency, размеру payload и нагрузке — по измерениям?
  7. Кто владеет API после релиза?

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

Семь пунктов ревью спецификации

1. Операции и семантика

Понятно ли, что является чтением, командой или созданием ресурса? Можно ли безопасно повторить запрос после таймаута?

2. Ошибки

Есть ли стабильные машинные коды, человекочитаемое описание, correlation ID и разделение клиентских и серверных ошибок?

3. Совместимость

Версия в URL — лишь один вариант. Можно использовать media type, заголовок или эволюционировать один контракт совместимыми изменениями. Важна политика: что считается breaking change и как проходит deprecation.

4. Пагинация и лимиты

Большие коллекции требуют ограничений. Offset pagination проста; курсор на устойчивом ключе сортировки помогает избежать части пропусков и дублей при изменениях. Само слово cursor не гарантирует согласованный снимок. Контракт должен задавать максимальный размер страницы, стабильный порядок с разрешением равенств и поведение при изменении данных между запросами.

5. Сбои и повторы

Нужны timeout, правила retry, idempotency key для неидемпотентных операций и поведение при частичном успехе.

6. Безопасность данных

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

7. Наблюдаемость

Нужно заранее договориться о request/correlation ID, метриках ошибок и latency, а также о владельце алертов.

Практика на Telegram-боте

На курсе я давал студентам задание собрать Telegram-бота на Flask. Webhook приносит POST с JSON, приложение проверяет входные данные и отправляет ответ через Telegram Bot API.

Небольшой проект заставляет пройти весь контракт: HTTP-методы, JSON, токен, timeout, ошибки и повторная доставка update. Теория быстро становится конкретной — запрос либо проходит по согласованному сценарию, либо нет.

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

Для проверки семантики полезны первичные источники: RFC 9110, SOAP 1.2, GraphQL over HTTP и документация gRPC.


Оригинал статьи: API от А до Я на Habr

Ваш ДПУПП

Похожие статьи