# 07. API и контракты

Как проектировать API как обещание: схемы, ошибки, идемпотентность, версионирование, совместимость и deprecation policy.

- **Author:** Фёдор Чищин
- **Level:** advanced
- **Reading time:** ~25 минут чтения
- **Published:** 2026-05-23
- **Source:** https://fedorthinks.com/ru/learn/software-architect-07-api-contracts

---

## 1. Главная идея

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

### 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`, нельзя завтра заменить это на произвольную строку.

## 2. Пример и антипример

Реальный мир повторяет запросы: 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?

Следующий модуль: [Модель данных](./software-architect-08-data-model).
