Обсудите принципы проектирования интерфейса API (REST/GraphQL/gRPC): как выбирать формат и уровни версионирования, какие ошибки обычно приводят к ломанию клиентов
Коротко и по делу — принципы проектирования API (REST / GraphQL / gRPC), выбор формата и уровни версионирования, и типичные ошибки, приводящие к ломанию клиентов. Выбор формата - REST — хорош для ресурсов и широкой совместимости (HTTP/JSON), простота кэша и проксирования. Подходит для публичных HTTP API и браузерных клиентов. - GraphQL — даёт гибкость клиенту в запросах, подходит когда клиенты требуют разные представления данных; сложнее кэширование и реальтайм-поддержка требует дополнительных паттернов. - gRPC — эффективен для внутренних микросервисов и стримингов (протокол бинарный, HTTP/2), требует прокси/шлюз для браузеров. Когда выбирать: - если важна простота и стандартное REST-поведение — REST; - если клиенты часто меняют состав полей и нужно минимизировать числа запросов — GraphQL; - если нужна высокая производительность, двоичный протокол и строгая контрактность — gRPC. Версионирование — уровни и подходы - URL-версия (пример: v1v1v1 в пути) — видимо и просто для кэширования, но мешает эволюции и смешивает маршруты; хорош для публичных стабильных API. - Заголовки/Content-Type (media-type versioning) — чисто с точки зрения URI, удобнее для эволюции, но сложнее для отладки/кэша. - Semver-подход для API (писать версии как major.minor.patchmajor.minor.patchmajor.minor.patch) — инкрементировать majormajormajor при несовместимых изменениях. - Ресурсный/эндпойнтный уровень — версионировать только ломаемые ресурсы, не весь API. - GraphQL — предпочитают один эволюционирующий схемный слой: избегать глобальных версий, использовать @deprecated + расписание удаления. - gRPC/protobuf — версии в package/name, жёсткая контрактность; эволюция через правила protobuf (добавлять новые поля безопасно, удалять — нет), резервирование тегов. Рекомендации по выбору: - публичное API с долгим сроком жизни: явное versioning (обычно URL или media-type) и строгая политика депрекации; - внутренние сервисы: gRPC + потребительские контракты и интеграционные тесты, реже URL-версии; - GraphQL: единая схема, депрекация полей и миграционные инструменты. Правила безопасной эволюции (общие) - Добавление необязательных полей — безопасно. - Никогда не удаляйте или переименовывайте существующие поля/параметры без депрекации и переходного периода. - Не менять типы полей (например, строка → число) — ломает парсинг. - Не менять семантику идентификаторов и единиц измерения. - Для enum: добавление новых значений обычно безопасно, но изменение номеров/значений — ломает; помечайте неизвестные значения как допустимые. - Для контрактов protobuf: никогда не переиспользуйте номера полей; используйте reserved для удалённых полей. - Документируйте и используйте схемы (OpenAPI/GraphQL schema/proto) как источник правды. Типичные ошибки, приводящие к ломанию клиентов - Удаление или переименование полей ответа. - Изменение типа поля (например, из JSON-объекта в строку) или формата дат. - Делание ранее необязательного поля обязательным без версии. - Перенумерация/повторное использование tag в protobuf. - Смена контрактных ошибок: изменение HTTP-кода (например, 200200200 → 204204204) или структуры тела ошибок; удаление полезного поля в теле ошибки. - Изменение пагинации (например, переход от offset→cursor без совместимости) или порядка сортировки без явного параметра. - Нарушение обратной совместимости в идентификаторах/ключах (формат id). - Изменение единиц измерения (секунды → миллисекунды) без обозначения. - Невнятная/несогласованная semantic поведения (например, PUT перестал быть идемпотентным). - Отсутствие контроля изменений и тестов на совместимость (нет contract/consumer tests). Практические рекомендации - Проектируйте с backward-совместимостью: делать поля опциональными, добавлять, а не удалять. - Вводите чёткую политику депрекации с таймлайном и инструментами для миграции. - Публикуйте схему/спецификацию (OpenAPI/GraphQL schema/proto) и changelog. - Пишите автотесты совместимости (consumer-driven contract tests). - Используйте feature flags и экспериментальные endpoints для крупных изменений. - При breaking change: обеспечьте migration plan, совместный период, и bump majormajormajor версии. Короткие примеры safe vs breaking - Safe: добавить поле ответа "middleName" (опционально). - Breaking: удалить поле "email" или поменять его формат (например, объект → строка). - Protobuf-safe: добавить поле с новым tag. Breaking: изменить tag/переназначить ранее удалённый tag. Итог: выбирайте формат по требованиям (совместимость vs гибкость vs производительность), старайтесь эволюционировать без ломания (добавления и депрекация), используйте схемы, контрактные тесты и чёткую политику версионирования и депрекации.
Выбор формата
- REST — хорош для ресурсов и широкой совместимости (HTTP/JSON), простота кэша и проксирования. Подходит для публичных HTTP API и браузерных клиентов.
- GraphQL — даёт гибкость клиенту в запросах, подходит когда клиенты требуют разные представления данных; сложнее кэширование и реальтайм-поддержка требует дополнительных паттернов.
- gRPC — эффективен для внутренних микросервисов и стримингов (протокол бинарный, HTTP/2), требует прокси/шлюз для браузеров.
Когда выбирать:
- если важна простота и стандартное REST-поведение — REST;
- если клиенты часто меняют состав полей и нужно минимизировать числа запросов — GraphQL;
- если нужна высокая производительность, двоичный протокол и строгая контрактность — gRPC.
Версионирование — уровни и подходы
- URL-версия (пример: v1v1v1 в пути) — видимо и просто для кэширования, но мешает эволюции и смешивает маршруты; хорош для публичных стабильных API.
- Заголовки/Content-Type (media-type versioning) — чисто с точки зрения URI, удобнее для эволюции, но сложнее для отладки/кэша.
- Semver-подход для API (писать версии как major.minor.patchmajor.minor.patchmajor.minor.patch) — инкрементировать majormajormajor при несовместимых изменениях.
- Ресурсный/эндпойнтный уровень — версионировать только ломаемые ресурсы, не весь API.
- GraphQL — предпочитают один эволюционирующий схемный слой: избегать глобальных версий, использовать @deprecated + расписание удаления.
- gRPC/protobuf — версии в package/name, жёсткая контрактность; эволюция через правила protobuf (добавлять новые поля безопасно, удалять — нет), резервирование тегов.
Рекомендации по выбору:
- публичное API с долгим сроком жизни: явное versioning (обычно URL или media-type) и строгая политика депрекации;
- внутренние сервисы: gRPC + потребительские контракты и интеграционные тесты, реже URL-версии;
- GraphQL: единая схема, депрекация полей и миграционные инструменты.
Правила безопасной эволюции (общие)
- Добавление необязательных полей — безопасно.
- Никогда не удаляйте или переименовывайте существующие поля/параметры без депрекации и переходного периода.
- Не менять типы полей (например, строка → число) — ломает парсинг.
- Не менять семантику идентификаторов и единиц измерения.
- Для enum: добавление новых значений обычно безопасно, но изменение номеров/значений — ломает; помечайте неизвестные значения как допустимые.
- Для контрактов protobuf: никогда не переиспользуйте номера полей; используйте reserved для удалённых полей.
- Документируйте и используйте схемы (OpenAPI/GraphQL schema/proto) как источник правды.
Типичные ошибки, приводящие к ломанию клиентов
- Удаление или переименование полей ответа.
- Изменение типа поля (например, из JSON-объекта в строку) или формата дат.
- Делание ранее необязательного поля обязательным без версии.
- Перенумерация/повторное использование tag в protobuf.
- Смена контрактных ошибок: изменение HTTP-кода (например, 200200200 → 204204204) или структуры тела ошибок; удаление полезного поля в теле ошибки.
- Изменение пагинации (например, переход от offset→cursor без совместимости) или порядка сортировки без явного параметра.
- Нарушение обратной совместимости в идентификаторах/ключах (формат id).
- Изменение единиц измерения (секунды → миллисекунды) без обозначения.
- Невнятная/несогласованная semantic поведения (например, PUT перестал быть идемпотентным).
- Отсутствие контроля изменений и тестов на совместимость (нет contract/consumer tests).
Практические рекомендации
- Проектируйте с backward-совместимостью: делать поля опциональными, добавлять, а не удалять.
- Вводите чёткую политику депрекации с таймлайном и инструментами для миграции.
- Публикуйте схему/спецификацию (OpenAPI/GraphQL schema/proto) и changelog.
- Пишите автотесты совместимости (consumer-driven contract tests).
- Используйте feature flags и экспериментальные endpoints для крупных изменений.
- При breaking change: обеспечьте migration plan, совместный период, и bump majormajormajor версии.
Короткие примеры safe vs breaking
- Safe: добавить поле ответа "middleName" (опционально).
- Breaking: удалить поле "email" или поменять его формат (например, объект → строка).
- Protobuf-safe: добавить поле с новым tag. Breaking: изменить tag/переназначить ранее удалённый tag.
Итог: выбирайте формат по требованиям (совместимость vs гибкость vs производительность), старайтесь эволюционировать без ломания (добавления и депрекация), используйте схемы, контрактные тесты и чёткую политику версионирования и депрекации.