ReST - PUT vs PATCH,以在添加新属性时最大限度地减少客户端和API之间的耦合



我们正在构建一组新的RESTAPI。

假设我们有一个具有以下字段的资源/users

{
id: 1
email: "test@user.com"
}

客户端实现此API,然后可以通过向PUT /users/1发送新的资源表示来更新此资源。

现在假设我们向模型添加一个新属性name,如下所示:

{
id: 1
email: "test@user.com"
name: "test user"
}

如果现有客户端正在使用的模型要调用我们的API而未更新,那么对PUT /users/1的调用将删除新的name属性,因为PUT应该替换资源。我知道客户端可以直接使用原始json,以确保它们始终接收添加到API中的任何新属性,但这是一项额外的工作,在正常情况下,客户端将在自己的一侧创建API资源的自己的模型表示。这意味着,每当添加任何新属性时,所有客户端都需要更新自己的代码/模型,以确保不会意外删除属性。这会在系统之间产生不必要的耦合。

作为解决这个问题的一种方法,我们正在考虑根本不实现PUT操作,并将更新切换到PATCH,在PATCH中,没有传入的属性根本不会更改。这在技术上似乎是正确的,但可能不符合REST的精神。我也有点担心客户端对PATCH动词的支持。

其他人是如何解决这个问题的?这里的最佳做法是什么?

您所处的环境需要某种形式的API版本控制。最合适的方法可能是每次更改时都使用新的媒体类型。

通过这种方式,您可以支持旧版本,PUT将完全合法。

如果你不想这样,只坚持使用PATCH,那么PATCH在任何地方都是受支持的,除非你使用古老的浏览器。没什么好担心的。

IMO,从PUT切换到PATCH不会解决您的问题。IMO的根本原因是,客户端已经考虑了为某个表示返回的数据遵循某种类型。根据Fielding

REST API不应该具有对客户端重要的"类型化"资源
(来源)

客户端应该使用内容类型协商来交换数据,而不是使用类型化资源。在这里,足够通用以获得广泛采用的媒体类型格式肯定是有益的,然而,某些领域可能需要更具体的表示格式。

想象一下汽车供应商的网页,在那里你可以从你喜欢的汽车中检索数据。作为一个人,你可以很容易地识别出数据描绘了一辆典型的汽车。然而,您最有可能在(HTML)中接收到数据的媒体类型并没有通过其语法或元素的语义说明数据描述了汽车,除非存在一些语义注释属性或元素,尽管您可能能够更新数据或在其他地方使用数据。

这是可能的,因为HTML附带了丰富的元素和属性规范,例如Web表单,这些表单不仅描述了支持或预期的输入参数,还描述了将数据发送到的URI、发送时使用的表示格式(由application/x-www-form-urlencoded隐式给出;但可能被enctype属性覆盖)或要使用的HTTP方法,其固定为HTML中的CCD_ 13或CCD_。通过这一点,服务器能够教会客户端如何构建请求。因此,除了必须了解HTTP、URI和HTML规范之外,客户端不需要知道任何其他内容。

由于网页通常充满了各种不相关的东西,如添加、样式信息或脚本,以及XML(类)语法,这并不是每个人都喜欢的,因为它可能会略微增加实际有效负载的大小,所以大多数所谓的";REST";API确实希望交换基于JSON的文档。虽然纯JSON不是一种理想的表示格式,因为它根本不支持链接,但它非常受欢迎。某些添加,如JSON超模式(application/schema+json hyper-schema)或JSON超文本应用语言(HAL)(application/hal+json),添加了对链接和链接关系的支持。这些可用于按原样呈现从服务器接收的数据。然而,如果您想要一个响应来自动驱动您的应用程序状态(即,用处理后的数据动态绘制GUI),则需要一种更具体的表示格式,该格式可以由您的客户端解析,并在了解服务器希望它用它做什么时相应地采取行动(=可供性)。如果你想指导客户如何构建请求,则需要支持其他媒体类型,如hal-forms或ion。某些媒体类型还允许您使用一个称为概要文件的概念,该概念允许您使用语义类型对资源进行注释。HAL JSON,即确实支持类似Content-Type报头现在可以包含诸如application/hal+json;profile=http://schema.org/Car的值的东西,该值提示媒体类型处理器有效载荷遵循给定简档的定义,并且因此可以应用进一步的有效性检查。

由于表示格式应该足够通用以获得广泛使用,并且URI本身也不应该提示客户端需要什么样的数据,因此需要使用其他机制。链接关系名称基本上是URI的注释,告诉客户端某个链接的用途。一个可分页的集合可能会返回用firstprevnextlast注释的链接,这些链接的作用非常明显。其他链接可能会用prefetch暗示,这暗示客户端可以在加载当前资源完成后立即加载资源,因为客户端很可能下一步会检索到该资源。然而,这种媒体类型应该是标准化的(在提案或RFC中定义,并在IANA注册),或者遵循Web链接提出的模式(即Dublin Core使用的模式)。如果服务器更改其URI方案,而不是试图从URI本身解析一些参数,那么只使用被调用链接关系名称的URI的客户端仍然可以工作。

关于分布式系统中的去耦合,必须存在一定量的耦合,否则各方根本无法通信。尽管这里的重点是,耦合应该基于大量客户端可能支持的定义良好的标准化格式,而不是交换只有极少数客户端支持的特定表示格式(在最坏的情况下,只有自己的客户端)。现在应该在各方可以用来交换格式的媒体类型上进行耦合,而不是直接耦合到API并使用未定义的基于JSON的语法(可能有相应字段语义的外部文档)。这里的问题不是应该问支持哪种媒体类型,而是你想支持多少。客户端或服务器支持的媒体类型越多,就越有可能与分布式系统中的其他对等方进行交互。总的来说,你希望一个服务器能够为过多的客户端提供服务,而一个客户端应该能够与每个服务器交互(在最好的情况下),而不需要不断采用。

因此,如果您真的想将客户端与服务器解耦,您应该仔细研究Web的实际工作方式,并尝试在应用程序层上模拟其交互模型。作为";鲍勃叔叔"Robert C.Martin提到

体系结构是关于意图的!(来源)

REST体系结构背后的意图是将客户端与服务器/服务解耦。因此,支持多种媒体类型(或定义自己的通用媒体类型以获得广泛采用),仅通过其附带的链接关系名称查找URI,依赖内容类型协商以及仅依赖所提供的数据,可以帮助您实现所需的解耦程度。


理论上一切都很好,但到目前为止,我在职业生涯中遇到的每个rest api都有预定义的合同,这些合同会随着时间的推移而变化。

这里的问题是,几乎所有所谓的";REST API";是RPC服务,其核心不应被称为"RPC服务";REST";首先,这是一个社区问题。通常,这样的API附带了外部文档(即Swagger),这些文档只是重新引入了经典RPC解决方案(如CORBA、RMI或SOAP)所面临的相同问题。在该过程中,文档可以被视为IDL,而不需要严格的骨架类;框架";使用某种类型的数据类,它们要么忽略最近引入的字段(在最好的情况下),要么在调用时完全崩溃。

REST面临的问题之一是,大多数人没有读过Fieldings的论文,因此没有看到REST试图建立的大局,而是声称知道REST是什么,因此混淆了事情,并将其服务称为RESTful,这导致了REST!=REST。那些指出什么是REST架构以及如何实现它的人被称为梦想家,而那些宣称错误术语(RPCoverHTTP=REST)的人则继续这样做,这增加了人们的困惑,尤其是那些刚刚了解整个问题的人。

我承认,开发一个真正的REST体系结构真的非常困难,因为引入某种形式的耦合太容易了。因此,需要进行非常仔细的设计,这需要时间,也需要花钱。很多公司不能或不想花的钱,尤其是在一个新技术定期发展的领域,负责开发此类解决方案的人往往在整个过程结束之前就离开了公司。

仅仅说它不应该被"键入"并不是一个真正可行的解决方案

由于浏览器无法与网页交互,您需要多久更改一次浏览器?我不谈论CSS的东西,也不谈论特定于浏览器的CSS或JS的东西。在过去的2-3年里,网络需要多久改变一次?与Web类似,REST体系结构旨在为未来几年的长期应用程序提供支持,通过设计实现自然进化。对于简单的前端2后端系统来说,这无疑是过分的。它开始发光,尤其是在有多个不在你控制之下的同龄人可以与你互动的情况下。

最新更新