1. Проблема
Развитие REST API — сложная задача, для решения которой доступно множество вариантов. В этой статье обсуждаются некоторые из этих вариантов.
2. Что в договоре?
Прежде всего, нам нужно ответить на один простой вопрос: что такое контракт между API и
клиентом?
2.1. URI являются частью контракта?
Давайте сначала рассмотрим структуру URI REST API — это часть контракта? Должны ли клиенты делать закладки, жестко кодировать и вообще полагаться на URI API?
Если это так, то взаимодействие Клиента со службой REST больше не будет управляться самой службой, а будет зависеть от того, что Рой Филдинг называет внеполосной
информацией:
REST API следует вводить без каких-либо предварительных знаний, кроме исходного URI (закладки) и набора стандартизированных типов мультимедиа, подходящих для целевой аудитории… Неудача здесь означает, что внеполосная информация управляет взаимодействием, а не гипертекстом.
Таким образом, URI не являются частью контракта ! Клиент должен знать только один URI — точку входа в API. Все другие URI должны быть обнаружены при использовании API.
2.2. Типы носителей являются частью контракта?
Как насчет информации о Типе носителя, используемой для представления Ресурсов, — это часть договора между Клиентом и Сервисом?
Чтобы успешно использовать API, Клиент должен иметь предварительные знания об этих типах мультимедиа . Фактически, определение этих типов носителей представляет собой весь договор.
Следовательно, именно здесь служба REST должна сосредоточиться больше всего:
API REST должен тратить почти все свои описательные усилия на определение типа (типов) мультимедиа, используемых для представления ресурсов и управления состоянием приложения, или на определение расширенных имен отношений и/или разметки с поддержкой гипертекста для существующих стандартных типов мультимедиа.
Таким образом, определения типа носителя являются частью контракта и должны быть заранее известны клиенту, использующему API. Здесь на помощь приходит стандартизация.
Теперь у нас есть хорошее представление о том, что такое контракт, давайте перейдем к тому, как на самом деле решить проблему управления версиями.
3. Опции высокого уровня
Давайте теперь обсудим высокоуровневые подходы к управлению версиями REST API:
- Управление версиями URI — версия пространства URI с использованием индикаторов версии.
- Media Type Versioning — версия представления ресурса
Когда мы вводим версию в пространстве URI, представления ресурсов считаются неизменяемыми. Поэтому, когда необходимо внести изменения в API, необходимо создать новое пространство URI.
Например, предположим, что API публикует следующие ресурсы — пользователей и привилегии:
http://host/v1/users
http://host/v1/privileges
Теперь давайте рассмотрим, что критическое изменение в пользовательском
API требует введения второй версии:
http://host/v2/users
http://host/v2/privileges
Когда мы верифицируем Media Type и расширяем язык, мы проходим Content Negotiation на основе этого заголовка. REST API будет использовать типы носителей MIME пользовательских поставщиков вместо общих типов носителей, таких как application/json
. Мы собираемся версионировать эти типы носителей вместо URI.
Например:
===>
GET /users/3 HTTP/1.1
Accept: application/vnd.myname.v1+json
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.myname.v1+json
{
"user": {
"name": "John Smith"
}
}
Мы можем ознакомиться с этой статьей «Пользовательские типы мультимедиа для Rest API» для получения дополнительной информации и примеров по этому вопросу.
Здесь важно понимать, что клиент не делает никаких предположений о структуре ответа , кроме того, что определено в типе носителя.
Вот почему общие типы носителей не идеальны. Они не предоставляют достаточно семантической информации и вынуждают клиента использовать дополнительные подсказки для обработки фактического представления ресурса.
Исключением является использование какого-либо другого способа уникальной идентификации семантики содержимого, например схемы XML.
4. Преимущества и недостатки
Теперь, когда у нас есть четкое представление о том, что является частью Контракта между Клиентом и Сервисом, а также общий обзор вариантов версии API, давайте обсудим преимущества и недостатки каждого подхода.
Во- первых, введение идентификаторов версии в URI приводит к очень большому объему URI. Это связано с тем, что любое критическое изменение в любом из опубликованных API приведет к созданию совершенно нового дерева представлений для всего API. Со временем это становится бременем для обслуживания, а также проблемой для клиента, у которого теперь есть больше возможностей для выбора.
Идентификаторы версий в URI также строго негибкие . Невозможно просто развить API отдельного ресурса или небольшого подмножества общего API.
Как мы упоминали ранее, это подход «все или ничего». Если часть API переходит на новую версию, то вместе с ней должен перейти и весь API. Это также делает обновление клиентов с версии 1 до версии 2 серьезной задачей, что приводит к более медленным обновлениям и гораздо более длительным периодам прекращения действия старых версий.
Кэширование HTTP также является серьезной проблемой, когда речь идет об управлении версиями.
С точки зрения прокси-кешей посередине каждый подход имеет свои преимущества и недостатки. Если URI версионный, то кеш должен хранить несколько копий каждого ресурса — по одной для каждой версии API. Это увеличивает нагрузку на кеш и снижает частоту попаданий в кеш, поскольку разные клиенты будут использовать разные версии.
Кроме того, некоторые механизмы аннулирования кеша больше не будут работать. Если тип носителя является версионным, то и клиент, и служба должны поддерживать HTTP-заголовок Vary, чтобы указать, что кэшируется несколько версий.
Однако с точки зрения клиентского кэширования решение, в котором версии типа носителя требуют немного больше работы, чем решение, в котором URI содержат идентификатор версии. Это связано с тем, что проще кэшировать что-то, когда его ключ является URL-адресом, чем типом мультимедиа.
Давайте закончим этот раздел определением некоторых целей (прямо из API Evolution ):
- не допускайте совместимых изменений в именах
- избегать новых основных версий
- делает изменения обратно совместимыми
- подумайте о совместимости вперед
5. Возможные изменения в API
Далее рассмотрим типы изменений REST API — они представлены здесь:
- изменения формата представления
- изменения ресурсов
5.1. Добавление к представлению ресурса
Документация по формату типа носителя должна быть разработана с учетом прямой совместимости. В частности, клиент должен игнорировать информацию, которую он не понимает (что JSON делает лучше, чем XML).
Теперь добавление информации в представление ресурса не нарушит работу существующих клиентов, если они правильно реализованы.
Продолжая наш предыдущий пример, добавление суммы
в представление пользователя
не будет критическим изменением:
{
"user": {
"name": "John Smith",
"amount": "300"
}
}
5.2. Удаление или изменение существующего представления
Удаление, переименование или общая реструктуризация информации в дизайне существующих представлений является критическим изменением для клиентов. Это потому, что они уже понимают и полагаются на старый формат.
Вот где вступает в действие согласование контента. Для таких изменений мы можем добавить новый тип носителя MIME поставщика.
Продолжим предыдущий пример. Скажем , мы хотим разбить имя
пользователя
на имя
и фамилию
:
===>
GET /users/3 HTTP/1.1
Accept: application/vnd.myname.v2+json
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.myname.v2+json
{
"user": {
"firstname": "John",
"lastname": "Smith",
"amount": "300"
}
}
Таким образом, это представляет собой несовместимое изменение для Клиента, которому придется запросить новое Представление и понять новую семантику. Однако пространство URI останется стабильным и не будет затронуто.
5.3. Основные семантические изменения
Это изменения смысла Ресурсов, отношения между ними или то, что отображается в бэкенде. Для таких изменений может потребоваться новый тип носителя или публикация нового родственного ресурса рядом со старым и использование ссылки для указания на него.
Хотя это звучит как повторное использование идентификаторов версий в URI, важным отличием является то, что новый ресурс публикуется независимо от любых других ресурсов в API и не будет разветвлять весь API в корне.
REST API должен соответствовать ограничению HATEOAS. В соответствии с этим большинство URI должны быть ОБНАРУЖЕНЫ клиентами, а не жестко запрограммированы. Изменение такого URI не следует рассматривать как несовместимое изменение. Новый URI может заменить старый, и клиенты смогут повторно обнаружить URI и продолжать функционировать.
Однако стоит отметить, что, хотя использование идентификаторов версий в URI проблематично по всем этим причинам, оно никоим образом не является несовместимым с REST.
6. Заключение
В этой статье была предпринята попытка дать обзор очень разнообразной и сложной проблемы развития REST-сервиса . Мы обсудили два распространенных решения, преимущества и недостатки каждого из них, а также способы анализа этих подходов в контексте REST.
Статья завершается аргументами в пользу второго решения — управления версиями типов мультимедиа при изучении возможных изменений в RESTful API.
Полную реализацию этого туториала можно найти в проекте GitHub .
7. Дополнительная литература
Обычно ссылки на эти ресурсы для чтения размещены на протяжении всей статьи, но в данном случае их просто слишком много: