Проект ЦИТадель
В архиве нашего подраздела хранится большая коллекция материалов об XML — языке, на котором веб собирался говорить в начале века. В итоге основным форматом обмена данными стал JSON, а проектирование HTTP API превратилось в повседневную работу разработчика. Сначала разберём сам формат, включая отсутствующие в нём типы и соглашения, которыми они заменяются. Затем рассмотрим API как контракт: ресурсы и методы, идемпотентность, проверяемую схему, эволюцию без поломки клиентов и альтернативы — gRPC, GraphQL и события. В практикуме предстоит спроектировать и описать небольшой API библиотеки.
В JSON есть шесть видов значений: объект, массив, строка, число, логическое значение и null. Объект хранит пары «имя — значение», массив — упорядоченную последовательность. Синтаксис невелик, а средства разбора существуют почти во всех распространённых языках. Структуры JSON естественно отображаются на словари, списки и скалярные значения, поэтому при обычном обмене данными не приходится выбирать между элементом и атрибутом, как в XML. JSON менее выразителен, но для этой задачи часто оказывается достаточно его небольшой переносимой модели.
Важно сразу назвать то, чего в JSON нет. Каждый такой пробел заполняется соглашением, и обе стороны API должны понимать его одинаково:
Z или явным смещением, например 2026-07-19T14:30:00Z. Календарная дата вроде дня рождения может не обозначать момент и записываться как 2026-07-19. Контракт должен различать эти случаи, а не добавлять часовой пояс к любой дате.Хороший HTTP API использует семантику протокола, а не придумывает её заново. Транспортная сторона HTTP разобрана в статье «HTTP/2 и HTTP/3»; здесь важны три элемента прикладного контракта:
/orders/17, а не /getOrderById. Иерархия уместна там, где она выражает устойчивую связь, например /orders/17/items. Идентификатор остаётся непрозрачным для клиента. Предметная команда тоже допустима, если она точнее описывает переход состояния, чем искусственное изменение набора полей.Retry-After, а POST после обрыва связи опасно повторять без знания результата.Если создание или другая операция не должны задваиваться, API может определить ключ идемпотентности. Клиент повторяет запрос с тем же ключом, а сервер связывает его с уже начатой или завершённой операцией. Контракт должен задать область уникальности ключа, срок хранения и поведение при другом теле с тем же значением. Тайм-ауты и повторы подробнее рассмотрены в статье «Надёжность поверх ненадёжной сети».
Тело ошибки тоже является частью контракта. RFC 9457 определяет Problem Details: машинно-разбираемый тип проблемы, краткий заголовок, статус, пояснение и адрес конкретного случая. Формат можно расширить, например списком ошибок полей, но внутреннюю трассу и закрытые сведения клиенту не передают.
API связывает несколько программ и нередко несколько команд, поэтому контракт должен существовать в машиночитаемой форме. OpenAPI описывает пути, методы, параметры, тела и ответы HTTP API. Формы данных задаются Schema Object, основанным в OpenAPI 3.1 на диалекте JSON Schema: в нём выражаются типы, обязательные поля и ограничения.
Из одной спецификации инструменты могут получить интерактивную документацию, клиентские и серверные заготовки, проверку запросов и ответов и часть контрактных тестов. Это происходит не само по себе: соответствующие стадии нужно включить в сборку, а возможности генератора — проверить на целевом языке. Подход «сначала спецификация» и получение описания из кода оба работают, если документ остаётся источником истины и меняется вместе с реализацией. Иначе он быстро превращается в устаревшую справку. Эта дисциплина продолжает идеи статьи «Инженерный конвейер».
?page=5&size=20, позволяет перейти к номеру страницы, но на изменяющихся данных записи могут повторяться или пропадать между запросами; глубокое смещение бывает дорогим для хранилища. Курсор — непрозрачный маркер «продолжить после этого места» — лучше подходит последовательному чтению меняющегося набора. Для него нужен стабильный полный порядок. Смещение остаётся полезным там, где пользователю действительно нужны страницы с номерами./v2/ проще всего увидеть и маршрутизировать. Старую версию отключают после объявления замены и срока, публикации способа миграции и измерения оставшегося использования.JSON поверх HTTP — распространённое исходное решение, но не обязательное. Альтернативы устраняют определённые затруднения и приносят собственные требования:
Сквозная задача — небольшой API библиотеки: книги, читатели и выдачи. Понадобятся выбранный язык, редактор OpenAPI и curl или другой HTTP-клиент.
Задание 1. Дизайн на бумаге. Спроектируйте ресурсы и пути. Решите, будет ли выдача самостоятельным ресурсом или подресурсом читателя, и обоснуйте выбор. Назначьте методы и статусы, включая «выдать книгу» и «продлить выдачу». Для создания предусмотрите защиту от задвоения после неопределённого сетевого исхода.
Задание 2. Спецификация. Опишите решение в OpenAPI: схемы сущностей, строковые идентификаторы, календарные даты и моменты времени, тела ошибок по RFC 9457 и курсорную пагинацию списка книг. Сгенерируйте документацию и проверьте, достаточно ли её для реализации клиента без устных пояснений.
Задание 3. Реализация и проверка. Реализуйте две или три операции с валидацией запросов по схеме. Проверьте корректный сценарий, ошибку валидации и повтор создания с тем же ключом идемпотентности. Повтор не должен создать второй ресурс; статус и тело определяются записанным контрактом и не обязаны совпадать побайтно.
Задание 4. Эволюция без поломки. Добавьте необязательное поле и убедитесь, что прежний клиент продолжает работать. Затем придумайте несовместимое изменение, например переименование поля, и оформите новую версию, период сосуществования и план отключения старой.
Задание 5*. Сравнение вариантов. Опишите операцию «выдать книгу» в Protocol Buffers, а получение читателя с его выдачами и книгами — в GraphQL. Для каждого варианта перечислите, что он упрощает и какие требования добавляет на этом небольшом примере.