API и контракты
Договорённость между приложением и сервером: что можно запросить, что придёт в ответ, что означают ошибки и что гарантированно продолжит работать завтра. API — это обещание, и старые версии вашего приложения держат вас за слово.
Почему это важно. Заставить всех обновиться невозможно. То, что вы выпустили год назад, до сих пор работает у кого-то на телефоне и сегодня обратится к вашему серверу. Поэтому почти любое решение о контракте принимается навсегда.
Что нужно понимать
- Что на самом деле нужно клиенту — в отличие от того, что просто лежит в базе
- На что вызывающая сторона может полагаться, а что вы вправе менять свободно
- Что ошибка велит клиенту сделать, а не только что именно пошло не так
- Как изменение доходит до старых клиентов
- Что является источником истины для формы данных — схема или две реализации, совпадающие по счастливой случайности
Основные темы
Проектирование поверхности API
- Ресурсы и операции; именование, которое переживёт генерацию клиентов на другом языке
- Гранулярность: болтливые походы на сервер против ответов, которые никто не читает
- Пагинация, фильтрация и сортировка — решённые один раз, а не заново на каждом endpoint
- Единообразие по всему API: оно важнее любого отдельного решения
Контракт
- Сгенерированные клиенты против написанных руками
- Схема как источник истины — OpenAPI или генератор, который владеет обеими сторонами
- Nullability, значения по умолчанию и неизвестные поля
- Enum и клиент, который встретил значение, добавленное уже после его выпуска
Ошибки
- Коды статуса, которые означают что-то конкретное
- Машиночитаемое тело ошибки, чтобы приложение могло разветвить логику
- Ошибки валидации, которые указывают на конкретное поле
- Различие между «вы сделали не так», «у нас сломалось» и «попробуйте позже»
Изменения со временем
- Аддитивное изменение как режим по умолчанию — и что вообще считается аддитивным
- Пометка устаревшим: со сроком и с измерениями
- Версионирование — и почему лучшая стратегия версионирования состоит в том, чтобы она почти не понадобилась
- Поддержка самого старого клиента, который ещё живёт у пользователей
Уровни
| Уровень | Как это выглядит |
|---|---|
| Junior | Пользуется существующими endpoint'ами и добавляет новые по сложившимся образцам. |
| Middle | Проектирует связную поверхность API, возвращает ошибки, на которые можно отреагировать, и сохраняет обратную совместимость. |
| Senior | Относится к контракту как к долгоживущему: планирует вывод старого из обращения, измеряет трафик от старых клиентов и строит API вокруг потребностей клиента, а не вокруг устройства схемы. |
Практика
Для начала
-
Сделайте одну ошибку осмысленной Возьмите безликий 400 и верните вместо него то, на что приложение сможет отреагировать и что сможет показать пользователю.
-
Приведите пагинацию к общему виду Сделайте так, чтобы у endpoint'а со списком была та же форма пагинации, что и у остального API.
-
Обработайте неизвестное значение enum Отправьте значение, которого клиент никогда не видел, и добейтесь, чтобы он деградировал плавно, а не падал.
Глубже
-
Добавьте поле, ничего не сломав Измените ответ так, чтобы годовалый клиент продолжил работать, и докажите это.
-
Объявите что-нибудь устаревшим Пометьте endpoint устаревшим, измерьте, кто всё ещё его вызывает, и запланируйте удаление.
-
Проектируйте от экрана Возьмите экран, который делает четыре запроса, спроектируйте для него один endpoint — а затем приведите аргументы за оба варианта.
Проверьте себя
- Какая самая старая версия вашего приложения всё ещё обращается к вашему серверу?
- Какие из изменений вашего API за последний год формально ломали совместимость?
- Отличает ли ваше приложение ошибку валидации от сбоя сервера и от превышения лимита запросов?
- Что происходит на клиенте, когда сервер добавляет новое значение в enum?
- Кто владеет формой вашего API — схема или две кодовые базы, которые пока что совпадают по случайности?
- Какой endpoint существует из-за устройства базы, а не из-за экрана?
Материалы
- Google API Design Guide — самое полное публичное руководство по проектированию связного API: именование, ошибки, стандартные методы. Пристрастное — и это идёт ему на пользу.
- RFC 9110: HTTP Semantics — что на самом деле означают методы и коды статуса. Сюда стоит идти, чтобы закрыть спор про 404 против 409.
- OpenAPI Specification — стоит знать, даже если клиентов вы генерируете иначе: это тот словарь, которым все остальные описывают контракт.
- Richardson Maturity Model — Мартин Фаулер о том, что обычно понимают под «REST» и что имелось в виду изначально. Коротко и снимает изрядную часть путаницы в спорах.
- Serverpod: working with endpoints — как контракт описывается и генерируется, когда обе стороны написаны на Dart.