API — это язык, на котором две системы договариваются. И как любой язык, он ценен предсказуемостью: если правила меняются на ходу, разговаривать невозможно.
Мы всегда начинаем с контракта, ещё до кода. Описываем ресурсы, методы, форматы, коды ошибок. Это занимает день-два и почти всегда вскрывает вопросы, которые иначе всплыли бы через месяц: что происходит, если объект удалён; можно ли создать две записи с одинаковым ключом; что возвращать, когда список пустой.
Побочная выгода: как только контракт согласован, фронтенд и бэкенд работают параллельно. Фронтенд поднимает заглушку по контракту и не ждёт готового сервера. На проектах со сжатыми сроками это часто решающая экономия.
Дальше — правила, которые мы соблюдаем почти без исключений.
Ломающие изменения — только через версию. Клиент не должен однажды утром обнаружить, что интеграция сломалась, потому что мы переименовали поле. Добавлять поля можно, удалять и переименовывать — нет. Если очень надо, появляется /v2, а /v1 живёт объявленный срок.
Формат ошибок фиксируется заранее и одинаков для всего API. Мы отдаём машиночитаемый код, человекочитаемое сообщение и, где уместно, указание на конкретное поле. Разница между «400 Bad Request» и «400 с кодом VALIDATION_FAILED и указанием, что поле phone не соответствует формату» — это разница между часом переписки с интегратором и нулём.
Идемпотентность для всего, что создаёт или списывает. Клиент отправил запрос, соединение оборвалось, клиент повторил — и создались два заказа. Лечится ключом идемпотентности: клиент передаёт уникальный идентификатор операции, сервер запоминает результат и на повтор отдаёт то же самое вместо новой записи. Это стоит недорого и снимает целый класс инцидентов.
Пагинация с самого начала, даже когда записей десять. Через год их будет сто тысяч, а эндпоинт без пагинации к этому моменту уже будет использоваться в трёх местах. Мы предпочитаем курсор смещению: он не сбивается, когда данные добавляются во время листания.
Даты — всегда в UTC и в ISO 8601. Это скучно и это спасает. Локальное время в API однажды обязательно приведёт к тому, что что-то произойдёт дважды в ночь перевода часов или не произойдёт вовсе.
Отдельная тема — документация. Наше единственное жёсткое требование: она должна лежать рядом с кодом и генерироваться из него. Документация, которая живёт в отдельном файле или, хуже, в вики, устаревает за месяц. После этого она хуже, чем её отсутствие: ей верят, а она врёт.
Чего мы стараемся избегать. Не делаем эндпоинты под конкретный экран интерфейса — через полгода экранов станет пять, и API превратится в свалку узкоспециальных методов. Не прячем ошибки за кодом 200 с полем success: false — HTTP уже умеет сообщать об ошибке, не нужно изобретать второй механизм поверх. Не возвращаем разную структуру в зависимости от параметров: клиенту придётся писать разбор на каждый случай.
И последнее, что стоит проговорить: хорошее API — это в первую очередь про то, чтобы им было тяжело воспользоваться неправильно. Если интегратор регулярно ошибается в одном и том же месте, дело не в интеграторе.
Понравилась статья?
У нас много таких историй — и ещё больше идей для вашего продукта.
Обсудить проектЧитать дальше
Как мы пришли к ScanClick
Продукт вырос из чужой боли, которую мы увидели на клиентском проекте: склад, тетрадь и три часа сверки каждый вечер.
Как мы держим нагрузку на Go-бэкенде
История одного сервиса: от 200 rps до 4000 rps без переписывания. Очереди, пулы и честный профайлинг.
Очереди и вебхуки: как не потерять сообщения
Идемпотентность, ретраи и dead-letter. Рассказываем, как строим надёжную обработку событий.