Software Architect · Модуль 07
API — это обещание между системами. Хороший контракт переживает клиентов, команды и несколько версий реализации.
REST · GraphQL · gRPC · idempotency · compatibility
Транспорт важен, но контракт важнее: что принимаем, что возвращаем, какие ошибки возможны и что можно безопасно повторять.
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, нельзя завтра заменить это на произвольную строку.
Реальный мир повторяет запросы: 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?