Перейти к основному содержимому

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.