如何处理REST API中新对象属性的引入 [英] How to handle the introduction of a new object property in a REST API

查看:58
本文介绍了如何处理REST API中新对象属性的引入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个公共的REST API,该API在所有公开的资源中公开了以下内容:

I have a public REST API that, among all the exposed resources, exposes these:

POST my/webservice/foos/
PUT my/webservice/foos/{fooId}

这两种资源都使用如下所示的JSON模型:

both resources use a JSON model like the following:

{
  "a": "a mandatory string",
  "b": "another mandatory string"
}

因此,每次用户创建新的Foo或更新现有的Foo时,他都必须同时指定新的a和新的b.到目前为止,一切都很好.

So, every time a user creates a new Foo or updates an existing one, he will have to specify both the new a and the new b. So far, so good.

现在,假设为示例起见,Foo实际上具有的功能远不止这两个属性,例如,它还具有字符串"c".该字符串c在其可能值的集合中承认为null.显然,该c属性还有其他设置方式.假设可以通过Web界面在现有的Foo上进行设置.

Now, suppose that a Foo has actually more than those two properties, say it has also a string "c", for the sake of the example. That string c admits null among the set of its possible values. Obviously, that c property has some other way to be set. Let's say it can be set on an existing Foo though a web interface.

让我们考虑一个具有a ="string a",b ="string b"和c ="string c"的Foo. 我将我的REST客户端称为此Foo对象上方的PUT函数,我的Foo的新状态为:a =新字符串a",b =新字符串b",c =字符串c",这很完美很好.

Let's consider a Foo having a="string a", b="string b" and c="string c". I call wih my REST client the PUT function above on this Foo object, and the new state of my Foo is: a="new string a", b="new string b", c="string c", and that's perfectly fine.

现在假设已部署REST API的更新,并且Foo的JSON模型还包括c:

Now suppose that an update to the REST API is deployed, and the JSON model for Foo includes also c:

{
  "a": "a mandatory string",
  "b": "another mandatory string",
  "c": "a new optional string"
}

如果使用与以前相同的客户端,并且使用与以前相同的JSON对象调用相同的PUT函数,则我的Foo将变为:a ="new string a",b ="new string b",c = null ,从客户的角度来看,这是出乎意料的.

If, with the same client as before, I call the same PUT function with the same JSON object as before, my Foo will become: a="new string a", b="new string b", c=null, which, from the client perspective, is quite unexpected..

我完全理解,引入一个非空的新属性将是API中的重大更改,这需要某种版本控制.但是您认为这种情况也应该被视为重大变化吗?我应该拥有仅更新a和b的v1版本,还是更新a,b和c的v2版本吗?有什么最佳实践可以避免增加此类更改的版本号?使用PATCH是切实可行的解决方案吗?

I totally underdstand that introducing a not-null new property would be a breaking change in the API that would require some kind of versioning. But do you think this case should be treated as a breaking change as well? Should I have a v1 version that only updates a and b, and a v2 that updates a, b and c? Is there any best practice to avoid increasing the version number for such changes? Is using PATCH a practical solution?

推荐答案

我完全理解,引入一个非空的新属性将是API中的重大更改,这需要某种版本控制.但是您认为这种情况也应该被视为重大变化吗?

I totally underdstand that introducing a not-null new property would be a breaking change in the API that would require some kind of versioning. But do you think this case should be treated as a breaking change as well?

仅当您的API基于RPC时. REST只是Web的一种概括,其设计旨在轻松适应变化. Web上的内容在不断变化,而不会破坏客户.网络如何实现这一目标?

Only if your API is RPC based. REST, which is just a generalization of the Web, is designed in a way to adapt to changes easily. Content on the Web is changing constantly without breaking clients. How does the Web achieve this?

尽管我通常同意Voice,但实际上我对此有一些不同的看法.在REST架构中,服务器或API应该教客户如何做事.客户端应如何知道服务器期望在某个终结点上收到什么? Swagger或类似的文档编制方法主要用于基于RPC的API ,但要使用REST有HATEOAS.在这里,超文本用于驱动应用程序状态.这意味着客户将使用收到的回复来探索新的可能性.

Although I usually agree with Voice, I have a bit different take on this one actually. In a REST architecture a server or API should teach a client on how to do things. How should a client know what a server expects to receive on a certain endpoint? Swagger or similar documentation approaches are mainly useful for RPC-based APIs, but REST has HATEOAS. Here, the hypertext is used to drive application state. This means that a client will use the response received to explore new possibilities.

Web中使用的传统交互流程是,客户端将调用其感兴趣的URI并接收响应,该响应将呈现给用户以对用户采取进一步的措施.在预期的用户输入的情况下,服务器将返回一个Web表单,用户可以在其中输入服务器期望的数据.服务器正是通过这种方式来教客户如何发送给服务器的.服务器当然会在接收时检查输入,以避免在期望的数字字段上输入字符串文字,并避免此类输入或验证是否已存在该数据元素.这样,客户端无需事先知道服务器可能期望的数据.如果您引入一个新字段,客户将在收到响应后学习该新字段.

A traditional interaction flow used in the Web is that a client will invoke a URI it is interested in and receive a response which is rendered for the user to take further actions upon. In case of expected user input the server will return a Web form where a user can enter data the server expects. This is exactly how clients are taught by the server what they have to send to the server. The server of course will check the input upon reception to avoid string literal inputs on expected number fields and such or validate whether such a data element already exists. This way a client does not need to know in advance what data the server might expect. If you introduce a new field the client will learn that new field upon reception of the response.

在REST体系结构中可以并且可能应该采用类似的方法.要么重用HTML表单,要么使用类似的方法,例如 halo + json 或定义自己的媒体类型,您应该向 IANA 注册.建议检查现有的媒体类型首先,看看他们是否提供您需要的功能.客户当然需要能够根据接收到的内容动态呈现表单.尽管这里可以使用很多浏览器作为参考,但这可能不是一件容易的事.

A similar approach can and probably should be taken in a REST architecture. Either reuse HTML forms, use some similar approaches like halo+json or define your own media type which you should register with IANA. It might be advisable to check for already existing media-types first and see if they offer the capability you need. A client, of course, needs to be able to dynamically render the form based on the received content. This might be a non-trivial task though plenty of browsers could be taken as reference here.

尽管HTML表单不支持PUTDELETEPATCH ,这些概念仍然可以应用于REST. IE.单击发送"按钮后,客户端通常会将所有以application/x-www-form-urlencoded表示形式的输入发送到服务器,除非 PUT 的定义使其可以: >

While HTML forms do not support PUT, DELETE or PATCH, the concepts still can be applied to REST. I.e. upon clicking the send button the client is usually sending all of the input in a application/x-www-form-urlencoded representation to the server, unless the client specifies an other enctype, and it is up to the server to transform the data to something it will further work upon, i.e. create a new resource, start a backing process or invoke a further service. PUT is defined in a way that allows it to either:

  • 重新配置目标资源以反映新的媒体类型
  • 在将PUT表示保存为新的资源状态之前,将其转换为与资源一致的格式
  • 使用415(不支持的媒体类型)响应拒绝该请求,该响应指示目标资源仅限于其当前媒体类型.
  • reconfigure the target resource to reflect the new media type
  • transform the PUT representation to a format consistent with that of the resource before saving it as the new resource state
  • reject the request with a 415 (Unsupported Media Type) response indicating that the target resource is limited to its current media type.

如何将资源的数据存储在服务器端通常是与客户端无关的实现细节.它可以简单地存储为包含描述内容的任意键和值的map或dictionary对象,也可以将其映射到面向对象编程中使用的某些类上.此外,它还可以作为纯文本存储在文件中或作为字节blob存储在数据库中.不过,理想情况下,服务器可以将资源的状态映射到不同的代表性媒体类型格式,这也使内容类型协商更加占优势,并避免了

How the data of a resource is stored at the server side is usually an implementation detail not of relevance to the client. It can be stored simply as map or dictionary object containing arbitrary keys and values that describe the content or it could be mapped onto certain classes used in object oriented programming. It could furthermore just be stored as plain text in a file or as byte blob in a database. Ideally though, a server can map the resource's state to different representational media-type formats, which also makes content type negotiation more dominant and avoids typed resources.

服务器的整个概念教导客户需要什么,并且客户能够根据接收到的数据动态呈现内容,从而使客户可以动态学习新知识并轻松适应变化.这实际上是REST体系结构的核心功能,也是为什么很多客户不受其控制的API应该针对这种体系结构的主要卖点之一.

This whole concept of a server teaching a client what it needs and the client being able to dynamically render the content based on the data received allows clients to learn new stuff on the fly and adapt to changes easily. This is actually a core feature of a REST architecture and one of the main selling points why APIs that have plenty of clients not under their control should aim for such an architecture.

我应该有一个仅更新a和b的v1版本,以及一个更新a,b和c的v2版本吗?

Should I have a v1 version that only updates a and b, and a v2 that updates a, b and c?

您可能会读到 Fielding对版本控制的看法直至:请勿(在将客户端可见的版本号粘贴在URI中的意义上).基本上,如果您遵循上述的server teaches, client learns方法,则除了API所有者自己的版本更改之外,实际上并不需要版本号. REST体系结构中的客户端无论如何都只会收到最新版本,或更确切地说,是服务器要向他们公开的版本,并且能够对其进行处理.如果服务器不同时分隔收到的数据,则只有期望服务器上具有特定版本的基于RPC的客户端才能适应这些更改.在这种情况下,通常最好使用通用名称空间的开关以避免混淆.

You might read about Fielding's take on versioning which simply boils down to: don't (in the sense of sticking client-visible version numbers inside URIs). Basically if you follow the server teaches, client learns approach outlined above there is no real need for versioning numbers anyway, except for the API owner's own sake of versioning changes. Clients in a REST architecture will only ever receive the latest version anyway, or to be more precise the version the server is exposing to them, and be able to handle it. Only RPC-based clients that expect a certain version on the server will have problems adapting to those changes if the server does not separate also the data they received. In such a case probably a switch of the general namespace is preferable in general to avoid confusion.

是否有最佳实践来避免增加此类更改的版本号?

Is there any best practice to avoid increasing the version number for such changes?

正如整个帖子中所概述的那样,服务器应教客户,而后者应在分析响应时探索他们的机会.这涉及到双方.即使您的服务器遵守REST体系结构施加的所有约束,客户端也可以解析和分析URI(而不使用链接关系名称),该客户端期望资源返回某些类型,而不是协商双方都理解的表示形式或我不愿意向服务器学习,而是将编程后的带外知识应用于客户端,这会随着时间的流逝而中断,因此将无法进一步与此类服务器进行交互.

As outlined throughout the post, server should teach clients while the latter one should explore their opportunities on analyzing responses. This is something that involves both parties. Even if you have a server respecting all of the constraints a REST architecture imposes, a client that parses and analyzes URIs rather than using link-relation names, that expects resources to return certain types instead of negotiating about the representation format both parties understand or that is unwilling to learn from a server and instead applies out-of-band knowledge programmed into the client will break over time and thus will not be able to interact with such a server further.

使用PATCH是否可行?

Is using PATCH a practical solution?

PATCH 通常是一种被误解的HTTP方法.它类似于编程中使用的补丁,其中补丁包含要应用到某些给定代码以将其转换为所需输出的更改.实际上应该通过HTTP进行相同的操作.客户端应采用资源的当前表示形式,并计算将资源的状态转换为所需状态所需的更改.通常忽略的修补程序的一个方面是,需要以原子方式应用修补程序.要么全部应用更改,要么都不应用.

PATCH is usually a misunderstood HTTP method. It is similar to patches used in programming where a patch contains changes to apply onto some given code to transform it to a desired output. The same should actually be done via HTTP. A client should take the current representation of the resource and calculate the changes necessary to transform the resource's state to the desired one. One aspect of patching that is usually neglected is, that patches need to be applied atomically. Either all of the changes are applied or none should be.

RFC 6902 中定义了application/json-patch+json一种倾向于传统修补的媒体类型.定义了一组指令,这些指令适用于作为JSON对象查看的资源.通过 JSON指针,可以在JSON补丁文档中解决在当前表示形式中要更改的各个段.

A media-type that leans towards traditional patching is application/json-patch+json defined in RFC 6902 which defines a set of instructions to apply onto a resource viewed as JSON object. Via JSON Pointers the respective segments to be changed within the current representation are addressed within the JSON Patch document.

RFC 7396 中定义了一种不同的修补方法,该方法定义了一种更实用的方法如何将更改应用于原始资源.这由application/merge-patch+json覆盖. JSON修补程序和JSON合并修补程序之间的区别在于,后者不定义要应用于文档的操作,而是在请求中包含整个更新的文档.因此,它依赖于一些固定的规则来应用. IE.如果新字段出现在请求中,则执行该字段的插入;如果现有字段的值发生更改,则会导致该字段的更新.删除是通过将值清零来实现的.

A different approach to patching is defined in RFC 7396 which defines a more practical approach on how to apply changes to the original resource. This is covered by application/merge-patch+json. The difference between JSON Patch and JSON Merge Patch is, that the latter one does not define operations to apply onto the document but contains the whole updated document within the request. It therefore relies on a number of fixed rules to apply. I.e. if a new field appears in the request than an insert of that field is performed while if the value of an existing field changes it results in an update of that field. Deletions are achieved through nulling out values.

可以在William Durand出色的博客文章

A more thorough explanation on how to patch can be found in William Durand excellent blog post Please do not patch like an idiot.

在实践中,如果您要进行部分更新以执行资源状态,则应使用PATCH,因为定义了PUT可以用提供的内容替换整个内容,当然还要进行一些有效性检查.尽管 PUT 还包含有关如何通过以下方式实现部分更新的提示重叠的资源,我猜这种技术并不常见.对于给定的在版本升级时添加新字段的方案,我认为打补丁不是您应该针对的正确方法,而是尝试使用server teaches, client learns方法作为整个答案的概述.

In practice PATCH should be used if you have partial updates to perform onto a resource's state as PUT is defined to replace the whole content with the one provided, after performing some validity checks of course. While PUT also contains a hint on how a partial update could achieved through overlapping resources, this technique is rather uncommon I guess. For the given scenario of adding new fields as of a version upgrade, I think that patching is not the right way you should aim for and instead attempt a server teaches, client learns approach as outline throughout this answer.

这篇关于如何处理REST API中新对象属性的引入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆