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

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

问题描述

我有一个公共 REST 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="new string a", b="new string b", c="string 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 表示中的所有输入发送到服务器,除非 client 指定了另一个enctype,就可以了到服务器将数据转换为它将进一步处理的内容,即创建新资源、启动支持过程或调用进一步的服务.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(不支持的媒体类型)响应拒绝请求,表明目标资源仅限于其当前媒体类型.

资源的数据如何存储在服务器端通常是与客户端无关的实现细节.它可以简单地存储为包含描述内容的任意键和值的映射或字典对象,或者它可以映射到面向对象编程中使用的某些类.此外,它可以仅作为纯文本存储在文件中或作为字节 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 中粘贴客户端可见的版本号的意义上).基本上,如果您遵循上面概述的服务器教导,客户端学习方法,则无论如何都没有真正需要版本号,除了 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 架构施加的所有约束的服务器,一个解析和分析 URIs 而不是使用链接关系名称的客户端,它期望资源返回某些类型而不是协商双方理解的表示格式或不愿意向服务器学习,而是将编程到客户端的带外知识应用到客户端会随着时间的推移而中断,因此将无法与这样的服务器进一步交互.

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.

一种倾向于传统修补的媒体类型是 application/json-patch+json 定义在 RFC 6902,它定义了一组指令以应用于被视为 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 Patch 和 JSON Merge Patch 之间的区别在于,后者没有定义应用于文档的操作,而是在请求中包含整个更新的文档.因此,它依赖于一些固定的规则来应用.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 还包含有关如何进行部分更新的提示通过重叠资源实现,我想这种技术相当罕见.对于在版本升级时添加新字段的给定场景,我认为修补不是您应该瞄准的正确方式,而是尝试使用 服务器教授,客户端学习 方法作为整个答案的概述.

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天全站免登陆