Подтвердить что ты не робот

Версии API REST

После чтения большого количества материалов по управлению версиями REST я использую управление версиями вызовов вместо API. Например:

http://api.mydomain.com/callfoo/v2.0/param1/param2/param3
http://api.mydomain.com/verifyfoo/v1.0/param1/param2

вместо того, чтобы сначала иметь

http://api.mydomain.com/v1.0/callfoo/param1/param2
http://api.mydomain.com/v1.0/verifyfoo/param1/param2

затем перейдем к

http://api.mydomain.com/v2.0/callfoo/param1/param2/param3
http://api.mydomain.com/v2.0/verifyfoo/param1/param2

Преимущество, которое я вижу:

  • При изменении звонка мне не нужно переписывать весь мой клиент - только те части, на которые влияют измененные вызовы.
  • Те части клиента, которые работают хорошо, могут продолжаться так же, как есть (у нас есть много часов тестирования, вложенных, чтобы гарантировать, что клиент и сторона сервера стабильны.)
  • Я могу использовать постоянные или непостоянные переадресации для измененных вызовов.
  • Обратная совместимость была бы легкой, поскольку я могу оставить старые версии вызовов как есть.

Мне что-то не хватает? Просьба сообщить.

4b9b3361

Ответ 1

Требовать HTTP-заголовок.

Version: 1

Заголовок Version предварительно зарегистрирован в RFC 4229 и есть некоторые законные причины, чтобы избежать использования префикса X или URI для использования. Более типичный заголовок был предложен yfeldblum в fooobar.com/questions/111289/...:

X-API-Version: 1

В любом случае, если заголовок отсутствует или не соответствует тому, что сервер может доставить, отправьте код ответа 412 Precondition Failed, а также причину сбоя. Это требует от клиентов указать версию, которую они поддерживают каждый раз, но обеспечивает согласованные ответы между клиентом и сервером. (При необходимости поддержка параметра запроса ?version= предоставит клиентам дополнительную гибкость.)

Этот подход прост, прост в реализации и совместим со стандартами.

Альтернативы

Я знаю, что некоторые очень умные, благими намерениями люди предложили URL-версию и согласование контента. Оба имеют значительные проблемы в определенных случаях и в том виде, в котором они обычно предлагаются.

Версии URL

Управление версиями URL-адресов конечных точек/службы работает, если вы управляете всеми серверами и клиентами. В противном случае вам придется обращаться к более старым клиентам, возвращаясь к более старым серверам, что вы в конечном итоге выполняете с пользовательскими заголовками HTTP, потому что системные администраторы серверного программного обеспечения, развернутого на гетерогенных серверах вне вашего контроля, могут делать всевозможные вещи, чтобы испортить URL-адреса, которые, по вашему мнению, будут легко анализироваться, если вы используете что-то вроде 302 "Перемещено временно" .

Консолидация контента

Консолидация содержимого с помощью заголовка Accept работает, если вы глубоко обеспокоены стандартом HTTP, но также хотите игнорировать то, что на самом деле говорят стандартные документы HTTP/1.1. Предложенный MIME-тип, который вы, как правило, видите, является чем-то вроде application/vnd.example.v1+json. Есть несколько проблем:

  • Бывают случаи, когда расширения для поставщиков действительно подходят, конечно, но немного другое поведение связи между клиентом и сервером не совсем соответствует определению нового "типа медиа". Кроме того, RFC 2616 (HTTP/1.1) читает: "Значения типа Media зарегистрированы в полномочии присвоенного номера Интернета. Процесс регистрации типа мультимедиа описан в RFC 1590. Использование не зарегистрированных типов носителей не рекомендуется". Я не хочу видеть отдельный тип медиа для каждой версии каждого программного продукта с REST API.
  • Любые диапазоны подтипов (например, application/*) не имеют смысла. Для API REST, которые возвращают структурированные данные клиентам для обработки и форматирования, что хорошо принимает */*?
  • Заголовок Accept предпринимает определенные усилия для правильного анализа. Там подразумевается и подразумеваемый и явный приоритет, который следует соблюдать, чтобы свести к минимуму обратное и будущее, необходимое для правильного ведения переговоров по содержанию. Если вы обеспокоены реализацией этого стандарта правильно, это важно для правильного.
  • RFC 2616 (HTTP/1.1) описывает поведение для любого клиента, который не включает заголовок Accept: "Если нет Accept поле заголовка присутствует, то предполагается, что клиент принимает все типы медиа". Таким образом, для клиентов вы не пишете себя (где у вас есть наименьший контроль), наиболее правильной задачей было бы реагировать на запросы, используя самую новую версию, наиболее подверженную взлому старой версии, которую знает сервер около. Другими словами, вы могли бы вообще не реализовывать управление версиями, и эти клиенты все равно будут разбиваться точно так же.

Отредактировано, 2014:

Я прочитал много других ответов и всех продуманных комментариев; Надеюсь, я смогу улучшить это с выгодой от пары лет обратной связи:

  • Не используйте префикс "X-" . Я думаю, что Accept-Version, вероятно, более значим в 2014 году, и есть некоторые веские опасения относительно семантики повторного использования Version, поднятого в комментариях. Наверное, они перекрываются с определенными заголовками, такими как Content-Version и относительной непрозрачностью URI, и я стараюсь быть осторожным в том, чтобы сбивать с толку два вопроса с вариациями согласования контента, которыми эффективно управляет заголовок Version. Третья "версия" URL https://example.com/api/212315c2-668d-11e4-80c7-20c9d048772b полностью отличается от "второй", независимо от того, содержит ли она данные или документ.
  • В отношении того, что я сказал выше об управлении версиями URL (например, оконечные точки, например https://example.com/v1/users), обратное, вероятно, имеет больше правды: если вы контролируете все серверы и клиенты, управление версиями URL/URI, вероятно, вам нужно. Для крупномасштабной службы, которая могла бы опубликовать один URL-адрес службы, я бы пошел с другой конечной точкой для каждой версии, как и большинство. На мое особое внимание в значительной степени влияет тот факт, что реализация, описанная выше, наиболее часто развертывается на множестве разных серверов множеством различных организаций и, что самое главное, на серверах, которые я не контролирую. Мне всегда нужен URL-адрес канонического сервиса, и если сайт все еще работает с версией API версии v3, я определенно не хочу, чтобы запрос https://example.com/v4/ возвращался со страницей 404 Not Found на своем веб-сервере (или, что еще хуже, 200 OK, который возвращает свою домашнюю страницу как 500k HTML по сотовым данным обратно в приложение iPhone.)
  • Если вам нужны очень простые/клиентские/реализации (и более широкое применение), очень сложно утверждать, что требование пользовательского заголовка в HTTP-запросе так же просто для авторов-клиентов, как GET -в URL-адрес ванили. (Хотя аутентификация часто требует, чтобы ваш токен или учетные данные были переданы в заголовках, в любом случае. Использование Version или Accept-Version в качестве секретного рукопожатия вместе с фактическим секретным рукопожатием подходит очень хорошо.)
  • Консолидация содержимого с использованием заголовка Accept хороша для получения разных типов MIME для одного и того же контента (например, XML против JSON против Adobe PDF), но не определена для версий этих вещей (Dublin Core 1.1 vs. JSONP против PDF/A). Если вы хотите поддерживать заголовок Accept, потому что важно уважать отраслевые стандарты, вам не нужно, чтобы созданный MIME-тип мешал согласованию типа носителя, который вам может понадобиться использовать в ваших запросах. Гарантированный запрет на использование API-версии API не должен мешать сильно используемому, часто цитируемому Accept, тогда как объединение их в одно и то же использование будет просто запутывать как для сервера, так и для клиента. Тем не менее, определение имен, которое вы ожидаете в именованном профиле в 2013 году RFC6906, предпочтительнее отдельного заголовка по множеству причин. Это довольно умно, и Я думаю, что люди должны серьезно рассмотреть этот подход.
  • Добавление заголовка для каждого запроса является одним из недостатков работы в протоколе без сохранения.
  • Вредоносные прокси-серверы могут делать почти все, чтобы уничтожить HTTP-запросы и ответы. Они не должны, и пока я не говорю о Cache-Control или В этом контексте все заголовки должны быть тщательно рассмотрены, как их контент потребляется в самых разных средах.

Ответ 2

Это вопрос мнения; здесь моя, вместе с мотивацией заключения.

  • включить версию в URL.
    Для тех, кто говорит, он принадлежит HTTP-заголовку, я говорю: может быть. Но включение URL-адреса - это принятый способ сделать это в соответствии с ранними лидерами в этой области. (Google, yahoo, твиттер и т.д.). Это то, что разработчики ожидают и делают то, что ожидают разработчики, другими словами, действуя в соответствии с принципом наименьшего удивления, вероятно, хорошая идея. Это абсолютно не делает его "труднее для клиентов обновлять". Если изменение URL-адреса каким-то образом представляет собой препятствие для разработчика потребляющего приложения, как предлагается в другом ответе здесь, этот разработчик должен быть уволен.

  • Пропустить малую версию
    Существует множество целых чисел. Ты не закончишь. Вам не нужно десятичное число. Любое изменение от 1.0 до 1.1 вашего API в любом случае не должно нарушать существующих клиентов. Поэтому просто используйте натуральные числа. Если вы хотите использовать разделение, чтобы подразумевать большие изменения, вы можете начать с v100 и делать v200 и т.д., Но даже там я думаю YAGNI, и это излишне.

  • Поставьте версию самой левой в URI
    Предположительно, в вашей модели будет много ресурсов. Все они должны исполняться синхронно. У вас не может быть людей, использующих v1 ресурса X и v2 ресурса Y. Он собирается что-то сломать. Если вы попытаетесь поддержать это, он создаст кошмар для обслуживания при добавлении версий, и в любом случае для разработчика не добавится значение. Итак, http://api.mydomain.com/v1/Resource/12345, где Resource - тип ресурса, а 12345 заменяется идентификатором ресурса.

Вы не спрашивали, но...

  • Опустить глаголы с вашего URL-адреса
    REST ориентирован на ресурсы. У вас есть такие вещи, как "CallFoo" в вашем URL-пути, который выглядит подозрительно, как глагол, и в отличие от существительного. Это не верно. Используйте Силу, Люк. Используйте глаголы, которые являются частью REST: GET PUT POST DELETE и т.д. Если вы хотите получить подтверждение на ресурсе, сделайте GET http://domain/v1/Foo/12345/verification. Если вы хотите обновить его, сделайте POST /v1/Foo/12345.

  • Поместить необязательные параметры как параметр запроса или полезную нагрузку
    Необязательные параметры не должны находиться в пути URL (перед первым вопросительным знаком), если вы не предполагаете, что эти необязательные параметры составляют автономный ресурс. Итак, POST /v1/Foo/12345?action=partialUpdate&param1=123&param2=abc.

Ответ 3

Не делайте ни одной из этих вещей, потому что они вставляют версию в структуру URI, и это будет иметь недостатки для ваших клиентских приложений. Это затруднит их обновление, чтобы использовать новые функции в вашем приложении.

Вместо этого вы должны использовать версию своих медиа-типов, а не URI. Это даст вам максимальную гибкость и эволюционные способности. Для получения дополнительной информации см. этот ответ Я дал другой вопрос.

Ответ 5

Facebook делает вывод в URL. Я чувствую, что правильность версий URL чище и легче поддерживать, а также в реальном мире.

.Net делает его очень простым в управлении версиями таким образом:

[HttpPost]
[Route("{version}/someCall/{id}")]
public HttpResponseMessage someCall(string version, int id))

Ответ 6

Это зависит от того, что вы называете версиями в вашем API, если вы вызываете версии для разных представлений (xml, json и т.д.) объектов, то вы должны использовать заголовки accept или настраиваемый заголовок. Именно так http предназначен для работы с представлениями. Это RESTful, потому что, если я вызываю один и тот же ресурс одновременно, но запрашивая разные представления, возвращаемые объекты будут иметь точно такую ​​же структуру информации и свойств, но с разным форматом, такой тип управления версиями является косметическим.

С другой стороны, если вы понимаете "версии" как изменения в структуре сущности, например, добавляя поле "возраст" к "пользовательской" сущности. Тогда вы должны подходить к этому с точки зрения ресурсов, который, на мой взгляд, является подходом RESTful. Как описано Роем Филдингом в его разложении... ресурс REST - это сопоставление от идентификатора к набору сущностей... Поэтому имеет смысл, что при изменении структуры объекта вам нужно иметь надлежащий ресурс, который указывает на то, что версия. Такое моделирование является структурным.

Я сделал аналогичный комментарий в: http://codebetter.com/howarddierking/2012/11/09/versioning-restful-services/

При работе с версией URL-версии версия должна появиться позже и не ранее в URL-адресе:

GET/DELETE/PUT onlinemall.com/grocery-store/customer/v1/{id}
POST onlinemall.com/grocery-store/customer/v1

Другой способ сделать это более чистым способом, но который может быть проблематичным при реализации:

GET/DELETE/PUT onlinemall.com/grocery-store/customer.v1/{id}
POST onlinemall.com/grocery-store/customer.v1

Выполнение этого способа позволяет клиенту запрашивать конкретный ресурс, который они хотят, который сопоставляет сущность, в которой они нуждаются. Без необходимости связываться с заголовками и настраиваемыми типами носителей, которые действительно проблематичны при реализации в производственной среде.

Также наличие URL-адреса в конце URL-адреса позволяет клиентам иметь больше детализации при выборе конкретных ресурсов, которые они хотят, даже на уровне метода.

Но самое главное с точки зрения разработчика, вам не нужно поддерживать все сопоставления (пути) для каждой версии ко всем ресурсам и методам. Это очень полезно, когда у вас много подзадач (встроенные ресурсы).

С точки зрения реализации, имеющей его на уровне ресурса, очень легко реализовать, например, если использовать Jersey/JAX-RS:

@Path("/customer")
public class CustomerResource {
    ...
    @GET
    @Path("/v{version}/{id}")
    public IDto getCustomer(@PathParam("version") String version, @PathParam("id") String id) {
         return locateVersion(version, customerService.findCustomer(id));
    }
    ...
    @POST
    @Path("/v1")
    @Consumes(MediaType.APPLICATION_JSON)
    public IDto insertCustomerV1(CustomerV1Dto customer) {
         return customerService.createCustomer(customer);
    }

    @POST
    @Path("/v2")
    @Consumes(MediaType.APPLICATION_JSON)
    public IDto insertCustomerV2(CustomerV2Dto customer) {
         return customerService.createCustomer(customer);
    }
...
}

IDto - это просто интерфейс для возврата полиморфного объекта, CustomerV1 и CustomerV2 реализуют этот интерфейс.