Software Architect · Модуль 07

API — это обещание между системами. Хороший контракт переживает клиентов, команды и несколько версий реализации.

REST · GraphQL · gRPC · idempotency · compatibility

§ 01

Транспорт важен, но контракт важнее: что принимаем, что возвращаем, какие ошибки возможны и что можно безопасно повторять.

API должен быть скучно предсказуемым

Хороший договор не впечатляет стилем. Он понятен, полон и не оставляет опасных двусмысленностей.

REST, GraphQL, gRPC и WebSocket решают разные задачи. REST прост для resource-oriented операций. GraphQL даёт клиентам гибкость формы ответа. gRPC полезен для typed service-to-service взаимодействия. WebSocket подходит для двустороннего real-time канала.

Но профессиональный API-дизайн начинается не с транспорта. Он начинается со схемы, статусов, ошибок, идемпотентности, rate limits, backwards compatibility и политики устаревания.

Ошибки — часть контракта

Если навигатор говорит только «не получилось», водитель не знает: кончился бензин, закрыта дорога или он ввёл неверный адрес.

Клиенту нужны machine-readable ошибки: код, сообщение для разработчика, поле, retryability, correlation id. Ошибка 400 Bad Request без структуры заставляет клиента парсить текст или гадать.

Ошибки должны быть стабильны так же, как успешный ответ. Если downstream-система строит поведение на PAYMENT_REQUIRES_ACTION, нельзя завтра заменить это на произвольную строку.

§ 02

Реальный мир повторяет запросы: timeout, retry, double click, webhook duplicate, restart worker-а.

Пример: идемпотентное создание платежа

Если человек дважды нажал кнопку вызова лифта, лифт не должен дважды приехать и уехать. Намерение одно.

Endpoint POST /payments принимает Idempotency-Key. Сервер хранит ключ, request fingerprint и результат операции. Повтор того же запроса возвращает тот же payment intent. Повтор с тем же ключом, но другим телом, возвращает конфликт.

Такой контракт защищает деньги, UX и поддержку. Клиент может безопасно делать retry после timeout.

Антипример: breaking change под видом cleanup

Если поменять замок в подъезде без предупреждения, проблема не в том, что новый замок лучше.

Команда переименовала поле userId в accountId, потому что так «правильнее». Внутренний frontend обновили, внешние клиенты сломались. Нет версии, changelog, migration window и deprecation warning.

Хороший API уважает уже выданные обещания. Новая модель может быть лучше, но путь миграции должен быть частью решения.

Самопроверка
  • Какие ошибки клиент может обработать автоматически? - Что будет при повторе запроса? - Как мы добавляем поле без breaking change? - Где описана deprecation policy?