Вопрос по api, rest – REST API управления версиями

31

После прочтения большого количества материала по версионированию 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

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

When the calls change, I do not have to rewrite my entire client - only the parts that are affected by the changed calls. Those parts of the client that work good can continue as is (we have a lot of testing hours invested to ensure both the client and the servers sides are stable.) I can use permanent or non-permanent redirects for calls that have changed. Backward compatiblity would be a breeze as I can leave older call versions as is.

Я что-то упустил? Пожалуйста, порекомендуйте.

youtube.com/watch?v=5WXYw4J4QOU Я думаю, что версионирование URL - лучший подход. inf3rno

Ваш Ответ

6   ответов
1

что вы называете версиями в вашем 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 реализуют этот интерфейс.

3

application/json; profile="http://www.myapp.com/schema/entity/v1"

Больше информации:

http://tools.ietf.org/html/rfc6906

http://buzzword.org.uk/2009/draft-inkster-profile-parameter-00.html

Как RFC 6906 и этот проект IETF связаны друг с другом? RFC 6906, кажется, является определенным стандартом, но он не говорит, как использоватьprofile вAccept или жеLink заголовки.
0

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

.Net упрощает создание версий таким способом:

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

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

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

Управление версиями должно быть связано с соответствующей и существующей техникой согласования контента, а не с самой фундаментальной структурой приложения RESTful (URI). В противном случае вы выплескиваете ребенка в ванну с водой при выполнении улучшений.
Если изменение URL-адреса каким-либо образом является препятствием для разработчика приложения-потребителя, как предлагается в ответе здесь, этот разработчик должен быть уволен.v1 в URL-адрес не является реальным препятствием для обновления разработчиков и "воспользоваться новыми функциями в вашем приложении."
В соответствии сstackoverflow.com/questions/389169/…некоторые прокси и промежуточные серверы удаляют заголовки (возможно, включая медиа-типы). Это может быть огромной проблемой. Или я не правильно понимаю? Ram Iyer
Я не могу себе представить, чтобы прокси или промежуточный сервер отбрасывал стандартный заголовок Accept.
Управление версиями может касаться не только контента, но и структуры и семантики API.
16

include the version in the URL.
For those who say, it belongs in the HTTP header, I say: maybe. But putting in the URL is the accepted way to do it according to the early leaders in the field. (Google, yahoo, twitter, and more). This is what developers expect and doing what developers expect, in other words acting in accordance with the principle of least astonishment, is probably a good idea. It absolutely does not make it "harder for clients to upgrade". If the change in URL somehow represents an obstacle to the developer of a consuming application, as suggested in a different answer here, that developer needs to be fired.

Skip the minor version
There are plenty of integers. You're not gonna run out. You don't need the decimal in there. Any change from 1.0 to 1.1 of your API shouldn't break existing clients anyway. So just use the natural numbers. If you like to use separation to imply larger changes, you can start at v100 and do v200 and so on, but even there I think YAGNI and it's overkill.

Put the version leftmost in the URI
Presumably there are going to be multiple resources in your model. They all need to be versioned in synchrony. You can't have people using v1 of resource X, and v2 of resource Y. It's going to break something. If you try to support that it will create a maintenance nightmare as you add versions, and there's no value add for the developer anyway. So, http://api.mydomain.com/v1/Resource/12345 , where Resource is the type of resource, and 12345 gets replaced by the resource id.

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

Omit verbs from your URL path
REST is resource oriented. You have things like "CallFoo" in your URL path, which looks suspiciously like a verb, and unlike a noun. This is wrong. Use the Force, Luke. Use the verbs that are part of REST: GET PUT POST DELETE and so on. If you want to get the verification on a resource, then do GET http://domain/v1/Foo/12345/verification. If you want to update it, do POST /v1/Foo/12345.

Put optional params as a query param or payload
The optional params should not be in the URL path (before the first question mark) unless you are suggesting that those optional params constitute a self-standing resource. So, POST /v1/Foo/12345?action=partialUpdate&param1=123&param2=abc.

Извините, я просто вижу это ... Хорошие моменты "Опустить глаголы ..." и & quot; Положить необязательные параметры ... & quot ;. Ram Iyer
Что касается вашего пункта 3. Я думаю, что более проблематично поместить версии в крайнее левое положение в URI, в REST вы работаете с ресурсами, которые сопоставляются с сущностями. Поскольку в большинстве случаев несколько ресурсов развиваются отдельно, например, пользователь имеет одно новое поле, которое не имеет ничего общего с клиентским ресурсом. С точки зрения реализации, если вы делаете это на корневом уровне в URL-адресе, вам необходимо добавлять новые сопоставления для всех ваших ресурсов, даже если они не изменились. Я думаю, что вы больше озабочены уровнем алгоритмов, использующих неправильные версии сущностей, но это не имеет ничего общего с версионированием REST API.
57

Version: 1

Версия заголовок предварительно зарегистрирован вRFC 4229 и есть некоторые законные причины избегать использования префикса X или специфичного для использования URI. Более типичный заголовок был предложенyfeldblum вhttps://stackoverflow.com/a/2028664:

X-API-Version: 1

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

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

Alternatives

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

URL Versioning

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

Content Negotiation

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

There are cases where the vendor extensions are actually appropriate, of course, but slightly different communication behaviors between client and server doesn't really fit the definition of a new 'media type'. Also, RFC 2616 (HTTP/1.1) reads, "Media-type values are registered with the Internet Assigned Number Authority. The media type registration process is outlined in RFC 1590. Use of non-registered media types is discouraged." I don't want to see a separate media type for every version of every software product that has a REST API. Any subtype ranges (e.g., application/*) don't make sense. For REST APIs that return structured data to clients for processing and formatting, what good is accepting */* ? The Accept header takes some effort to parse correctly. There's both an implied and explicit precedence that should be followed to minimize the back-and-forth required to actually do content negotiation correctly. If you're concerned about implementing this standard correctly, this is important to get right. RFC 2616 (HTTP/1.1) describes the behavior for any client that does not include an Accept header: "If no Accept header field is present, then it is assumed that the client accepts all media types." So, for clients you don't write yourself (where you have the least control), the most correct thing to do would be to respond to requests using the newest, most prone-to-breaking-old-versions version that the server knows about. In other words, you could have not implemented versioning at all and those clients would still be breaking in exactly the same way.

Edited, 2014:

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

Don't use an 'X-' prefix. I think Accept-Version is probably more meaningful in 2014, and there are some valid concerns about the semantics of re-using Version raised in the comments. There's overlap with defined headers like Content-Version and the relative opaqueness of the URI for sure, and I try to be careful about confusing the two with variations on content negotiation, which the Version header effectively is. The third 'version' of the URL https://example.com/api/212315c2-668d-11e4-80c7-20c9d048772b is wholly different than the 'second', regardless of whether it contains data or a document. Regarding what I said above about URL versioning (endpoints like https://example.com/v1/users, for instance) the converse probably holds more truth: if you control all servers and clients, URL/URI versioning is probably what you want. For a large-scale service that could publish a single service URL, I would go with a different endpoint for every version, like most do. My particular take is heavily influenced by the fact that the implementation as described above is most commonly deployed on lots of different servers by lots of different organizations, and, perhaps most importantly, on servers I don't control. I always want a canonical service URL, and if a site is still running the v3 version of the API, I definitely don't want a request to https://example.com/v4/ to come back with their web server's 404 Not Found page (or even worse, 200 OK that returns their homepage as 500k of HTML over cellular data back to an iPhone app.) If you want very simple /client/ implementations (and wider adoption), it's very hard to argue that requiring a custom header in the HTTP request is as simple for client authors as GET-ting a vanilla URL. (Although authentication often requires your token or credentials to be passed in the headers, anyway. Using Version or Accept-Version as a secret handshake along with an actual secret handshake fits pretty well.) Content negotiation using the Accept header is good for getting different MIME types for the same content (e.g., XML vs. JSON vs. Adobe PDF), but not defined for versions of those things (Dublin Core 1.1 vs. JSONP vs. PDF/A). If you want to support the Accept header because it's important to respect industry standards, then you won't want a made-up MIME Type interfering with the media type negotiation you might need to use in your requests. A bespoke API version header is guaranteed not to interfere with the heavily-used, oft-cited Accept, whereas conflating them into the same usage will just be confusing for both server and client. That said, namespacing what you expect into a named profile per 2013's RFC6906 is preferable to a separate header for lots of reasons. This is pretty clever, and I think people should seriously consider this approach. Adding a header for every request is one particular downside to working within a stateless protocol. Malicious proxy servers can do almost anything to destroy HTTP requests and responses. They shouldn't, and while I don't talk about the Cache-Control or Vary headers in this context, all service creators should carefully consider how their content is consumed in lots of different environments.
Если вы анализируете заголовок Accept, вам нужно правильно обработать 1) упорядочение, 2) подстановочные знаки и 3) «коэффициент качества», который изменяет взвешенные значения при вычислении того, какой ответ нужно вернуть. Больше наw3.org/Protocols/rfc2616/rfc2616-sec14.html
Хорошо написан! Я хотел бы получить дополнительные разъяснения по вашим четырем пунктам в конце. Вы говорите: «Это важно, чтобы понять правильно», но что правильно в этом случае?
@JoeLiversedge Спасибо за отличный ответ, мне очень понравилось читать ваши взгляды на эти разные методы. Интересно, не могли бы вы расширить свой ответ, чтобы поделиться своим взглядом на метод с использованием профиля-носителя, который Мэтью поделился в другом ответе?
Похоже, именно то, что вы не должны делать сами, но использовать библиотеку со встроенным алгоритмом для.
Насколько я понимаю из ссылки, которую вы предоставляете с w3, заголовок Version предназначен для использования для создания версий объектов, а не ресурсов, он говорит: ... Когда редактируемый объект повторно отправляется с использованием, например, PUT, это поле дает значение версии. Это, как правило, позволяет серверу проверять, например, что две одновременные модификации разными сторонами не будут потеряны ... Похоже, что это заголовок для определения изменений в данных, не входящих в структуру, идея API-версий - иметь обратную совместимость по структуре ваших ресурсов не об истории данных.

Похожие вопросы