Дизайн API
Ты опубликовал в ответе поле «процент скидки». Через полгода на него завязались мобильное приложение, партнёрский кабинет, склад и три внутренних сервиса. Теперь бизнес хочет скидки в рублях, а поле убрать нельзя: оно больше не твоё. Контракт живёт дольше команды, которая его написала.
Стержень: наружу выносят необходимый минимум, версия даёт право на несовместимое изменение, а правила игры описывают наравне со структурой полей.
// Формулировки: «как назовёшь ресурсы?», «зачем версионирование?», «мобильному нужен один набор полей, вебу другой»
Контракт - не зеркало базы
Ресурс называют существительным, действие выражает метод: GET /orders/17, POST /orders, DELETE /orders/17. Как только в путь попадает глагол, начинается разнобой: /getOrder, /createOrder, /orderCreate, /order/new - четыре стиля в одном сервисе, и каждый новый разработчик добавляет пятый.
Правило глубже звучит так: контракт говорит на языке предметной области, а не на языке таблиц. Если наружу торчат имена колонок, то нормализация, переименование поля или переезд на другое хранилище мгновенно становятся проблемой всех потребителей - а их бывает тридцать, и половина чужие.
// Проверка на месте: можно ли переписать хранение с нуля, не тронув контракт? Если нет - внутреннее устройство протекло наружу, и теперь оно зацементировано.
Каждое поле - обязательство
Добавить поле в ответ безопасно: старые клиенты его просто не читают. Убрать - ломающее изменение для каждого, кто его читает, и узнаёшь ты об этом в момент выката, по звонку.
Отсюда правило: наружу выносят минимум, нужный потребителю сегодня. Поля «на будущее» - худший вид долга: пока они пустые, никто не жалуется, а через год кто-нибудь построит на них отчёт, и убрать станет нельзя.
// Обратная крайность - отдавать заказ вместе с двумястами позициями и всей историей статусов. Если позиция весит около 300 байт, ответ тянет на 60 килобайт, из которых экран показывает три поля. На мобильной сети это лишняя секунда на ровном месте и лишняя нагрузка на базу при каждом открытии.
Что ломает клиента, а что нет
Совместимо: новое необязательное поле в ответе, новый необязательный параметр запроса, новый метод рядом со старым. Клиент этого не заметит.
Ломает: удаление поля, переименование, смена типа (число стало строкой), новое обязательное поле в запросе, сужение допустимых значений. Отдельная ловушка - новое значение перечисления: добавил статус «частично отгружен», и клиент со строгим разбором падает на незнакомом слове, хотя формально ты ничего не удалял.
// Версия нужна ровно для ломающих изменений: v1 и v2 живут параллельно, потребителям дают срок на переход, дата отключения v1 объявлена сразу. Версия без даты отключения означает две версии навсегда и двойную поддержку.
Правила игры важнее полей
Структуру полей клиент разберёт сам по примеру ответа. Чего он не угадает: сколько запросов в минуту ему можно, что придёт при превышении, за сколько метод обязан ответить, что произойдёт при повторе.
Ограничение частоты пишут числом: «100 запросов в минуту на клиента; сверх лимита - код 429 и заголовок со временем ожидания». Без этой строчки клиент проектирует нагрузку вслепую и знакомится с лимитом в бою, в пятницу вечером.
// Вторая обязательная строчка - идемпотентность для операций, меняющих состояние. Клиент отправил создание платежа, ответ потерялся в сети, клиент повторил запрос. Без ключа идемпотентности человек заплатит дважды, и разбирать это будет поддержка.
- идемпотентность
- повтор того же запроса с тем же ключом даёт тот же результат и не создаёт вторую сущность
Как отвечать: «Клиенту нужны данные из трёх сервисов на одном экране. Что предложишь?»
Собирать на сервере - шлюзом или отдельным агрегирующим сервисом. Три запроса с клиента тоже работают, но считаем: список из двадцати заказов, к каждому нужен клиент и статус доставки - это 1 плюс 20 плюс 20, сорок один запрос. На мобильной сети один заход стоит около 200 миллисекунд, последовательно получается больше восьми секунд. Вторая причина важнее скорости: клиент начинает знать про внутреннюю нарезку системы, и перенос функции между сервисами превращается в релиз мобильного приложения. Агрегатор эту нарезку прячет. Важное ограничение - он не должен обрастать бизнес-логикой, иначе станет ещё одним монолитом, который знает про всех. Его дело - сходить, собрать, отдать.
Кандидат считает цену альтернативы в секундах, называет причину про связанность и сразу ставит границу новому компоненту.
На чём валятся
- −− Проектируют контракт зеркалом схемы базы и цементируют хранение.
- −− Публикуют поля «на будущее», которые потом нельзя убрать.
- −− Вкладывают в ответ все связанные сущности и раздувают его в десятки килобайт.
- −− Считают новое значение перечисления безопасным изменением.
- −− Заводят отдельный метод под каждый экран приложения.
- −− Не описывают лимиты, таймауты и идемпотентность - клиент узнаёт о них в бою.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Что стоит вынести в контракт, а что оставить внутренней деталью сервиса?A)Всё внутреннее устройство — для прозрачностиB)Только то, что нужно потребителюC)Всё, что может понадобиться в будущемD)Ровно те поля, что есть в таблицах
показать ответ и разбор
+B)Только то, что нужно потребителю// разбор: Каждое поле в контракте — обязательство поддерживать его годами: кто-то на него завяжется, и убрать станет нельзя. Поэтому наружу выносят необходимый минимум, а внутреннее устройство прячут. Добавить поле позже легко и обратно совместимо, убрать — почти невозможно.
- Метод возвращает заказ вместе с полным списком позиций и историей статусов. В чём риск?A)Ответ распухает и тянет лишние данныеB)Клиент не сможет разобрать вложенностьC)Нарушается принцип именования ресурсовD)Метод перестанет быть идемпотентным
показать ответ и разбор
+A)Ответ распухает и тянет лишние данные// разбор: У заказа могут быть сотни позиций и десятки смен статуса, а списку заказов это всё не нужно. Ответ раздувается, база нагружается, а клиент выбрасывает большую часть. Обычное решение — отдавать вложенное отдельными ресурсами либо позволять клиенту явно запрашивать нужный состав.
- Что описать в контракте, кроме структуры запроса и ответа?A)Язык реализации сервисаB)Схему таблиц внутри сервисаC)Число экземпляров сервисаD)Ограничения по частоте вызовов
показать ответ и разбор
+D)Ограничения по частоте вызовов// разбор: Потребителю нужно знать не только форму данных, но и правила игры: сколько запросов в секунду допустимо, что вернётся при превышении, каковы таймауты и как быстро сервис обычно отвечает. Без этого клиент проектирует нагрузку вслепую и узнаёт про лимиты, уперевшись в них в бою.
- Мобильному клиенту нужен один набор полей, веб-интерфейсу — другой. Что предложить?A)Отдавать всем максимально полный ответB)Завести два разных метода на каждый экранC)Дать параметр выбора состава ответаD)Хранить предпочтения клиента на сервере
показать ответ и разбор
+C)Дать параметр выбора состава ответа// разбор: Явный параметр состава решает задачу, не плодя методы: клиент просит то, что ему нужно. Альтернатива — отдельный слой агрегации под каждый тип клиента. Метод на каждый экран плох тем, что интерфейс начинает повторять устройство приложения и множится с каждым новым макетом.
- Почему интерфейс, повторяющий структуру базы, считают плохим решением?A)Он работает медленнее прочихB)Любое изменение хранения ломает клиентовC)Его труднее описать спецификациейD)Он не поддерживает версионирование
показать ответ и разбор
+B)Любое изменение хранения ломает клиентов// разбор: Если контракт зеркалит таблицы, то нормализация, переименование колонки или смена хранилища немедленно становятся проблемой всех потребителей. Контракт должен говорить на языке предметной области и оставаться стабильным, пока внутреннее устройство свободно меняется под нагрузку.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.