【问题标题】:Use of PUT vs PATCH methods in REST API real life scenarios在 REST API 实际场景中使用 PUT 与 PATCH 方法
【发布时间】:2022-12-24 01:03:55
【问题描述】:

首先,一些定义:

PUT 在Section 9.6 RFC 2616 中定义:

PUT 方法请求将封闭的实体存储在提供的 Request-URI 下。如果 Request-URI 引用一个已经存在的资源,则包含的实体应该被视为驻留在原始服务器上的版本的修改版本.如果 Request-URI 不指向现有资源,并且该 URI 能够被请求用户代理定义为新资源,则源服务器可以使用该 URI 创建资源。

PATCH定义在RFC 5789

PATCH 方法要求一组变化中描述的 请求实体应用于由 Request- 标识的资源 网址。

同样根据RFC 2616 Section 9.1.2 PUT 是幂等的,而 PATCH 不是。

现在让我们来看一个真实的例子。当我使用数据{username: 'skwee357', email: 'skwee357@domain.example'} POST 到/users 并且服务器能够创建资源时,它将以 201 和资源位置(假设为/users/1)进行响应,并且下一次调用 GET /users/1 将返回{id: 1, username: 'skwee357', email: 'skwee357@domain.example'}

现在假设我想修改我的电子邮件。电子邮件修改被认为是“一组更改”,因此我应该用“patch document”修补/users/1。在我的例子中,它将是 JSON 文档:{email: 'skwee357@newdomain.example'}。然后服务器返回 200(假设权限没问题)。这让我想到第一个问题:

  • PATCH 不是幂等的。它在 RFC 2616 和 RFC 5789 中是这样说的。但是,如果我发出相同的 PATCH 请求(使用我的新电子邮件),我将获得相同的资源状态(我的电子邮件被修改为请求的值)。为什么 PATCH 不是幂等的?

PATCH 是一个比较新的动词(RFC 于 2010 年 3 月引入),它来解决“修补”或修改一组字段的问题。在引入 PATCH 之前,大家都使用 PUT 来更新资源。但是引入PATCH之后,让我很困惑PUT是干什么用的。这让我想到了第二个(也是主要的)问题:

  • PUT 和 PATCH 之间的真正区别是什么?我在某处读到 PUT 可能用于代替特定资源下的整个实体,因此应该发送完整的实体(而不是像 PATCH 那样的属性集)。这种情况的实际用途是什么?您什么时候想替换/覆盖特定资源 URI 上的实体,为什么不考虑更新/修补实体这样的操作?我看到的 PUT 的唯一实际用例是在集合上发出 PUT,即 /users 以替换整个集合。在引入 PATCH 之后,在特定实体上发布 PUT 就没有意义了。我错了吗?

【问题讨论】:

标签: json rest http put http-method


【解决方案1】:

笔记:当我第一次花时间阅读有关 REST 的内容时,幂等性是一个难以理解的概念。正如进一步的 cmets(和Jason Hoetger's answer)所示,我在最初的回答中仍然没有完全正确。有一段时间,我一直拒绝广泛更新这个答案,以避免有效地剽窃 Jason,但我现在正在编辑它,因为,嗯,我被要求这样做(在 cmets 中)。

看完我的回答后,我建议你也阅读Jason Hoetger's excellent answer这个问题,我会尽量让我的回答更好,而不是简单地从 Jason 那里窃取。

为什么 PUT 是幂等的?

正如您在 RFC 2616 引文中指出的那样,PUT 被认为是幂等的。当您 PUT 资源时,这两个假设在起作用:

  1. 您指的是一个实体,而不是一个集合。

  2. 您提供的实体是完整的(全部的实体)。

    让我们看看你的一个例子。

    { "username": "skwee357", "email": "skwee357@domain.example" }
    

    如果您按照您的建议将此文档发布到/users,那么您可能会取回一个实体,例如

    ## /users/1
    
    {
        "username": "skwee357",
        "email": "skwee357@domain.example"
    }
    

    如果以后要修改此实体,请在 PUT 和 PATCH 之间进行选择。 PUT 可能如下所示:

    PUT /users/1
    {
        "username": "skwee357",
        "email": "skwee357@gmail.com"       // new email address
    }
    

    您可以使用 PATCH 完成相同的操作。这可能看起来像这样:

    PATCH /users/1
    {
        "email": "skwee357@gmail.com"       // new email address
    }
    

    您会立即注意到这两者之间的区别。 PUT 包含该用户的所有参数,但 PATCH 仅包含正在修改的参数 (email)。

    使用 PUT 时,假设您要发送完整的实体,而那个完整的实体取代该 URI 中的任何现有实体。在上面的示例中,PUT 和 PATCH 实现了相同的目标:它们都更改了该用户的电子邮件地址。但是 PUT 通过替换整个实体来处理它,而 PATCH 只更新提供的字段,而其他字段不受影响。

    由于 PUT 请求包括整个实体,如果您重复发出相同的请求,它应该总是有相同的结果(您发送的数据现在是实体的全部数据)。因此 PUT 是幂等的。

    使用 PUT 错误

    如果在 PUT 请求中使用上述 PATCH 数据会发生什么情况?

    GET /users/1
    {
        "username": "skwee357",
        "email": "skwee357@domain.example"
    }
    PUT /users/1
    {
        "email": "skwee357@gmail.com"       // new email address
    }
    
    GET /users/1
    {
        "email": "skwee357@gmail.com"      // new email address... and nothing else!
    }
    

    (出于这个问题的目的,我假设服务器没有任何特定的必填字段,并且会允许这种情况发生......实际上可能并非如此。)

    由于我们使用了 PUT,但只提供了 email,现在这是该实体中唯一的东西。这导致数据丢失。

    这个例子是为了说明目的——永远不要真正这样做(除非你的意图是删除省略的字段,当然......那么你正在使用 PUT,因为它应该被使用)。这个 PUT 请求在技术上是幂等的,但这并不意味着它不是一个糟糕的、失败的想法。

    PATCH如何做到幂等?

    在上面的例子中,补丁曾是幂等的。您进行了更改,但如果您一次又一次地进行相同的更改,它总是会返回相同的结果:您将电子邮件地址更改为新值。

    GET /users/1
    {
        "username": "skwee357",
        "email": "skwee357@domain.example"
    }
    PATCH /users/1
    {
        "email": "skwee357@gmail.com"       // new email address
    }
    
    GET /users/1
    {
        "username": "skwee357",
        "email": "skwee357@gmail.com"       // email address was changed
    }
    PATCH /users/1
    {
        "email": "skwee357@gmail.com"       // new email address... again
    }
    
    GET /users/1
    {
        "username": "skwee357",
        "email": "skwee357@gmail.com"       // nothing changed since last GET
    }
    

    我原来的例子,为了准确性而修正

    我最初有我认为显示非幂等性的例子,但它们具有误导性/不正确。我将保留示例,但使用它们来说明不同的事情:针对同一实体的多个 PATCH 文档,修改不同的属性,不会使 PATCH 非幂等。

    假设在过去的某个时间添加了一个用户。这是你开始的状态。

    {
      "id": 1,
      "name": "Sam Kwee",
      "email": "skwee357@olddomain.example",
      "address": "123 Mockingbird Lane",
      "city": "New York",
      "state": "NY",
      "zip": "10001"
    }
    

    在 PATCH 之后,您有一个修改后的实体:

    PATCH /users/1
    {"email": "skwee357@newdomain.example"}
    
    {
      "id": 1,
      "name": "Sam Kwee",
      "email": "skwee357@newdomain.example",    // the email changed, yay!
      "address": "123 Mockingbird Lane",
      "city": "New York",
      "state": "NY",
      "zip": "10001"
    }
    

    如果您随后重复应用您的 PATCH,您将继续得到相同的结果:电子邮件已更改为新值。 A 进去,A 出来,因此这是幂等的。

    一个小时后,在你去泡咖啡休息一下之后,其他人带着他们自己的 PATCH 来了。邮局似乎一直在做出一些改变。

    PATCH /users/1
    {"zip": "12345"}
    
    {
      "id": 1,
      "name": "Sam Kwee",
      "email": "skwee357@newdomain.example",  // still the new email you set
      "address": "123 Mockingbird Lane",
      "city": "New York",
      "state": "NY",
      "zip": "12345"                      // and this change as well
    }
    

    由于这个来自邮局的 PATCH 本身不关心电子邮件,只关心邮政编码,如果重复应用,它也会得到相同的结果:邮政编码被设置为新值。 A进去,A出来,所以这是幂等的。

    第二天,您决定再次发送 PATCH。

    PATCH /users/1
    {"email": "skwee357@newdomain.example"}
    
    {
      "id": 1,
      "name": "Sam Kwee",
      "email": "skwee357@newdomain.example",
      "address": "123 Mockingbird Lane",
      "city": "New York",
      "state": "NY",
      "zip": "12345"
    }
    

    您的补丁具有与昨天相同的效果:它设置了电子邮件地址。 A 进去了,A 出来了,所以这也是幂等的。

    我在原来的答案中错了什么

    我想做出一个重要的区分(我在最初的回答中弄错了)。许多服务器将通过发回新的实体状态以及您的修改(如果有)来响应您的 REST 请求。所以,当你得到这个回复回来就不一样了从你昨天拿回来的那个,因为邮政编码不是您上次收到的那个。但是,您的请求与邮政编码无关,只与电子邮件有关。所以你的 PATCH 文档仍然是幂等的——你在 PATCH 中发送的电子邮件现在是实体上的电子邮件地址。

    那么 PATCH 什么时候不是幂等的呢?

    对于这个问题的完整处理,我再次向您推荐Jason Hoetger's answer,它已经完全回答了这个问题。

【讨论】:

  • 这句话不太正确:“但它是幂等的:只要 A 进去,B 总是出来”。例如,如果您在邮局更新邮政编码之前发送至GET /users/1,然后在邮局更新后再次发出相同的GET /users/1请求,您将得到两个不同的响应(不同的邮政编码)。进入相同的“A”(GET 请求),但您得到不同的结果。然而 GET 仍然是幂等的。
  • @DanLowe:绝对可以保证 GET 是幂等的。它在 RFC 2616 的第 9.1.2 节和更新的规范 RFC 7231 section 4.2.2 中明确指出,“在本规范定义的请求方法中,PUT、DELETE 和安全请求方法是幂等的。”幂等性并不意味着“每次发出相同的请求都会得到相同的响应”。 7231 4.2.2 继续说:“重复请求将具有相同的预期效果,即使最初的请求成功了,尽管反应可能不同。
  • @JasonHoetger 我承认这一点,但我不明白它与这个答案有什么关系,它讨论了 PUT 和 PATCH,甚至从未提到 GET ...
  • “这个 PUT 请求在技术上是幂等的”——是的,但它发送了错误的数据(即丢失的数据),这就是重点。好作品。
  • 啊,@JasonHoetger 的评论澄清了这一点:只有结果状态,而不是多个幂等方法请求的响应需要相同。
【解决方案2】:

尽管 Dan Lowe 的出色回答非常彻底地回答了 OP 关于 PUT 和 PATCH 之间的区别的问题,但它对为什么 PATCH 不是幂等的问题的回答并不完全正确。

为了说明为什么 PATCH 不是幂等的,它有助于从幂等的定义开始(来自Wikipedia):

术语幂等更广泛地用于描述如果执行一次或多次将产生相同结果的操作 [...] 幂等函数是具有属性 f(f(x)) = f(x) 的函数任意值 x。

用更易于理解的语言,幂等 PATCH 可以定义为:在使用补丁文档对资源进行 PATCH 之后,所有使用相同补丁文档对同一资源的后续 PATCH 调用都不会更改该资源。

相反,非幂等操作是 f(f(x)) != f(x),对于 PATCH 可以表示为:在使用补丁文档 PATCH 资源后,后续 PATCH 调用相同的资源相同的补丁文件改变资源。

为了说明非幂等 PATCH,假设有一个 /users 资源,并假设调用 GET /users 返回用户列表,当前:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

假设服务器允许 PATCHing /users,而不是像 OP 示例中那样 PATCHing /users/{id}。让我们发出这个 PATCH 请求:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

我们的补丁文档指示服务器将名为newuser 的新用户添加到用户列表中。第一次调用后,GET /users 会返回:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

现在,如果我们发出完全一样的PATCH请求如上,会发生什么? (为了这个例子,我们假设 /users 资源允许重复的用户名。)“op”是“add”,所以一个新用户被添加到列表中,随后的 GET /users 返回:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

/users 资源已更改再次, 即使我们发布了完全一样的补丁针对完全一样的端点。如果我们的 PATCH 是 f(x),则 f(f(x)) 与 f(x) 不同,因此,这个特定的 PATCH 不是幂等的.

虽然 PATCH 不是保证要做到幂等,PATCH 规范中没有任何内容可以阻止您在特定服务器上进行所有 PATCH 操作幂等。 RFC 5789 甚至预见到了幂等 PATCH 请求的优势:

可以以幂等的方式发出 PATCH 请求, 这也有助于防止两个人之间的碰撞造成不良后果 PATCH 在相似的时间范围内请求相同的资源。

在 Dan 的示例中,他的 PATCH 操作实际上是幂等的。在那个例子中, /users/1 实体在我们的 PATCH 请求之间发生了变化,但没有因为我们的 PATCH 请求;它实际上是邮局的不同的导致邮政编码更改的补丁文件。 Post Office不同的PATCH是不同的操作;如果我们的 PATCH 是 f(x),那么邮局的 PATCH 就是 g(x)。幂等性指出 f(f(f(x))) = f(x),但不保证 f(g(f(x)))

【讨论】:

  • 假设服务器还允许在 /users 发出 PUT,这也会使 PUT 非幂等。所有这一切归结为服务器是如何设计来处理请求的。
  • 因此,我们只能使用 PATCH 操作构建 API。那么,使用 http VERBS 对 Resources 进行 CRUD 操作的 REST 原则是什么?先生们,我们不是把 PATCH 边界过于复杂了吗?
  • 如果 PUT 是在一个集合上实现的(例如/users),任何 PUT 请求都应该替换该集合的内容。所以 PUT 到 /users 应该期望一组用户并删除所有其他用户。这是幂等的。你不太可能在 /users 端点上做这样的事情。但是像 /users/1/emails 这样的东西可能是一个集合,允许用一个新集合替换整个集合可能是完全有效的。
  • 我绝不会考虑针对集合发布 PATCH,仅发布和删除。这真的做过吗?因此,PATCH 是否可以在所有实际用途中被视为幂等?
  • 但是为什么要使用 PATCH 将用户添加到用户集合中呢?您基本上是在创建一个新资源(新用户),难道不应该通过 POST 请求来完成吗?这让我很困惑。
【解决方案3】:

TLDR - 简化版

=> 为现有资源设置所有新属性。

修补=> 部分更新现有资源(并非所有属性都需要)。

【讨论】:

  • 另外:PATCH => 可以是指令而不仅仅是更新的属性
  • 如果 gal 易于更新,我们为什么要发送现有资源的所有属性?为什么检查它很重要?而不是简单地更新发送的字段?
  • PUT 不要求资源存在。虽然使用 POST 创建和 PUT 更新是一种常见的模式,但 RFC 表示“PUT 方法请求创建目标资源的状态,或者用包含在请求消息内容。”您可以很好地设计一个 API,以便 PUT /users/1 HTTP/1.1 将创建 ID 为 1 的用户(如果不存在),如果存在则替换为 ID 1 的用户。
【解决方案4】:

我对此也很好奇,并发现了一些有趣的文章。我可能无法全面回答您的问题,但这至少提供了更多信息。

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFC 指定 PUT 必须采用全新的资源 作为请求实体的表示。这意味着如果例如 只提供了某些属性,那些应该被删除(即设置 为空)。

鉴于此,PUT 应该发送整个对象。例如,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.example'}

这将有效地更新电子邮件。 PUT 可能不太有效的原因是您真正修改一个字段并包括用户名有点无用。下一个示例显示了差异。

/users/1
PUT {id: 1, email: 'newemail@domain.example'}

现在,如果 PUT 是根据规范设计的,那么 PUT 会将用户名设置为 null,您将得到以下信息。

{id: 1, username: null, email: 'newemail@domain.example'}

当您使用 PATCH 时,您只更新您指定的字段,并像您的示例一样保留其余字段。

以下对 PATCH 的看法与我以前从未见过的略有不同。

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

PUT 和 PATCH 请求之间的区别体现在 服务器处理封闭实体以修改资源的方式 由 Request-URI 标识。在 PUT 请求中,封闭的实体 被认为是存储在 源服务器,客户端请求存储的版本 更换。但是,对于 PATCH,封闭的实体包含一组 说明资源当前如何驻留在 应修改原始服务器以生成新版本。补丁 方法影响 Request-URI 标识的资源,它也 可能对其他资源有副作用;即,新资源可能是 通过应用 PATCH 创建或修改现有的。

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

您或多或少将 PATCH 视为更新字段的一种方式。因此,您不是发送部分对象,而是发送操作。即用值替换电子邮件。

文章就此结束。

值得一提的是,PATCH 并不是真正为真正的 REST 而设计的 API,因为 Fielding 的论文没有定义任何方法来部分 修改资源。但是,Roy Fielding 自己说 PATCH 是 [他] 为最初的 HTTP/1.1 提案创建的东西,因为 部分 PUT 永远不是 RESTful。当然你不是在传输一个完整的 表示,但 REST 不要求表示是 无论如何完成。

现在,我不知道我是否像许多评论员指出的那样特别同意这篇文章。发送部分表示很容易成为对更改的描述。

对我来说,我对使用 PATCH 的看法很复杂。在大多数情况下,我会将 PUT 视为 PATCH,因为到目前为止我注意到的唯一真正区别是 PUT“应该”将缺失值设置为 null。这可能不是“最正确”的方法,但祝你编码完美。

【讨论】:

  • 可能值得添加:在 William Durand 的文章(和 rfc 6902)中有一些例子,其中“op”是“add”。这显然不是幂等的。
  • 或者,您可以更轻松地改用 RFC 7396 合并补丁,并避免构建补丁 JSON。
  • 对于 nosql 表,patch 和 put 之间的区别很重要,因为 nosql 没有列
【解决方案5】:

tl;博士版本

  • 邮政: 习惯了创造实体

  • :用于更新/替换您所在的现有实体必须发送整个表示您希望存储的实体

  • 修补: 习惯了更新你所在的实体只发送需要更新的字段

【讨论】:

  • 为什么发送所有字段以进行更新很重要?
  • @jossefaz 因为您想替换整个资源。
  • 那么您认为我们可以将您对 PUT 的回答更新为“用于更新/替换现有实体”吗?
  • 谢谢@jossefaz,我更新了我的答案
  • 简而言之=我们在这里想要的方式。应该是公认的答案。
【解决方案6】:

PUT 和 PATCH 的区别在于:

  1. PUT 必须是幂等的。为了实现这一点,您必须将整个完整资源放在请求正文中。
  2. PATCH 可以是非幂等的。这意味着它在某些情况下也可以是幂等的,例如您描述的情况。

    PATCH 需要一些“补丁语言”来告诉服务器如何修改资源。调用者和服务器需要定义一些“操作”,如“添加”、“替换”、“删除”。例如:

    GET /contacts/1
    {
      "id": 1,
      "name": "Sam Kwee",
      "email": "skwee357@olddomain.example",
      "state": "NY",
      "zip": "10001"
    }
    
    PATCH /contacts/1
    {
     [{"operation": "add", "field": "address", "value": "123 main street"},
      {"operation": "replace", "field": "email", "value": "abc@myemail.example"},
      {"operation": "delete", "field": "zip"}]
    }
    
    GET /contacts/1
    {
      "id": 1,
      "name": "Sam Kwee",
      "email": "abc@myemail.example",
      "state": "NY",
      "address": "123 main street",
    }
    

    补丁语言可以通过定义如下约定使其隐含,而不是使用显式的“操作”字段:

    在 PATCH 请求正文中:

    1. 字段的存在意味着“替换”或“添加”该字段。
    2. 如果一个字段的值为空,则表示删除该字段。

      根据上述约定,示例中的 PATCH 可以采用以下形式:

      PATCH /contacts/1
      {
        "address": "123 main street",
        "email": "abc@myemail.example",
        "zip":
      }
      

      看起来更加简洁和用户友好。但是用户需要了解基本约定。

      通过我上面提到的操作,PATCH 仍然是幂等的。但是如果你定义像这样的操作:“increment”或“append”,你可以很容易地看到它不再是幂等的。

【讨论】:

    【解决方案7】:

    在我看来,幂等性意味着:

    • 放:

    我发送了一个完整的资源定义,因此 - 生成的资源状态与 PUT 参数所定义的完全相同。每次我用相同的 PUT 参数更新资源时——结果状态都是完全一样的。

    • 补丁:

    我只发送了部分资源定义,因此其他用户可能会同时更新此资源的其他参数。因此 - 具有相同参数及其值的连续补丁可能会导致不同的资源状态。例如:

    假设一个对象定义如下:

    车: - 颜色:黑色, - 类型:轿车, - 座位:5

    我修补它:

    {红色'}

    结果对象是:

    车: - 红色, - 类型:轿车, - 座位:5

    然后,其他一些用户给这辆车打上了补丁:

    {类型:'两厢车'}

    所以,结果对象是:

    车: - 红色, - 类型:掀背车, - 座位:5

    现在,如果我再次修补这个对象:

    {红色'}

    结果对象是:

    车: - 红色, - 类型:掀背车, - 座位:5

    与我之前得到的有什么不同!

    这就是为什么 PATCH 不是幂等的而 PUT 是幂等的。

    【讨论】:

      【解决方案8】:

      让我更仔细地引用和评论RFC 7231 section 4.2.2,已经在早期的 cmets 中引用过:

      如果请求方法的预期效果在 使用该方法的多个相同请求的服务器是相同的 作为单个此类请求的效果。请求方法 本规范定义的 PUT、DELETE 和安全请求方法 是幂等的。

      (...)

      幂等方法是有区别的,因为请求可以是 如果通信失败之前发生自动重复 客户端能够读取服务器的响应。例如,如果一个 客户端发送 PUT 请求,底层连接关闭 在收到任何响应之前,客户端可以建立一个新的 连接并重试幂等请求。它知道重复 该请求将具有相同的预期效果,即使原始请求 请求成功,但响应可能不同。

      那么,一个幂等方法在多次请求后应该“相同”的是什么?不是服务器状态,也不是服务器响应,而是预期效果.特别是,该方法“从客户端的角度来看”应该是幂等的。现在,我认为这个观点表明 Dan Lowe's answer 中的最后一个示例(我不想在这里抄袭)确实表明 PATCH 请求可以是非幂等的(以比中的示例更自然的方式) Jason Hoetger's answer)。

      实际上,让我们通过使明确的成为可能来使示例稍微更精确打算对于第一个客户。假设这个客户通过项目的用户列表来检查他们的电子邮件邮政编码。他从用户 1 开始,注意到 zip 是正确的,但电子邮件是错误的。他决定用一个完全合法的 PATCH 请求来纠正这个问题,并且只发送

      PATCH /users/1
      {"email": "skwee357@newdomain.example"}
      

      因为这是唯一的更正。现在,请求由于某些网络问题而失败,并在几个小时后自动重新提交。同时,另一个客户端(错误地)修改了用户 1 的 zip。然后,第二次发送相同的 PATCH 请求并没有实现预期效果客户端,因为我们最终得到了不正确的 zip。因此,该方法在 RFC 的意义上不是幂等的。

      相反,如果客户端使用 PUT 请求来更正电子邮件,将用户 1 的所有属性与电子邮件一起发送到服务器,即使稍后必须重新发送请求并且用户 1 已被修改,他的预期效果也会达到同时 --- 因为第二个 PUT 请求将覆盖自第一个请求以来的所有更改。

      【讨论】:

        【解决方案9】:

        其他人都回答了 PUT 与 PATCH。我只是想回答原始问题标题的哪一部分:“......在 REST API 现实生活场景中”。在现实世界中,这发生在我身上的互联网应用程序有一个 RESTful 服务器和一个带有“宽”客户表(大约 40 列)的关系数据库。我错误地使用了 PUT,但我认为它就像一个 SQL 更新命令,并没有填写所有的列。问题:1) 有些列是可选的(因此空白是有效答案),2) 许多列很少更改,3) 不允许用户更改某些列,例如上次购买日期的时间戳,4) 一列是免费的-form 文本“评论”栏,用户努力填写半页客户服务 cmets,如配偶姓名,询问或通常订单,5)我当时正在开发互联网应用程序,担心数据包大小。

        PUT 的缺点是它迫使您发送大量信息(所有列包括整个 Comments 列,即使只更改了一些内容)和 2 个以上用户同时编辑同一客户的多用户问题(最后一个一个按更新获胜)。 PATCH 的缺点是您必须跟踪更改内容的视图/屏幕端,并具有一些智能以仅发送更改的部分。 Patch 的多用户问题仅限于编辑同一客户的相同列。

        【讨论】:

          【解决方案10】:

          考虑到您关于幂等性的问题,我可能有点偏离主题,但我希望您考虑进化性。

          考虑你有以下元素:

          {
            "username": "skwee357",
            "email": "skwee357@domain.example"
          }
          

          如果你用 PUT 修改,你必须给出对象的完整表示:

          PUT /users/1
          {
            "username": "skwee357",
            "email": "skwee357@newdomain.example"
          }
          

          现在更新模式,并添加一个字段 phone

          PUT /users/1
          {
            "username": "skwee357",
            "email": "skwee357@newdomain.example",
            "phone": "123-456-7890"
          }
          

          现在用同样的方式用 PUT 再次更新它,它会将 phone 设置为 null。为避免这种不良副作用,您必须在每次更新架构时更新所有修改元素的组件。瘸。

          通过使用 PATCH,您不会遇到这个问题,因为 PATCH 只更新给定的字段。所以,在我看来,你应该使用 PATCH 来修改一个元素(无论是真的幂等与否)。那是现实生活中的经验回报。

          【讨论】:

            【解决方案11】:

            方法非常适合以表格格式更新数据,例如在关系数据库或实体(如存储)中。根据用例,它可用于部分更新数据或替换整个实体。这将始终是幂等的。

            修补方法可用于更新(或重组)存储在本地文件系统或非 sql 数据库中的 json 或 xml 格式的数据。这可以通过在请求中提及要执行的操作/操作来执行,例如将键值对添加/删除/移动到 json 对象。 remove 操作可用于删除键值对,重复请求将导致错误,因为之前删除了键,使其成为非幂等方法。 json 数据补丁请求请参考RFC 6902

            这个artical有PATCH方法相关的详细信息。

            【讨论】:

            • 感谢您的文章链接。 IN 揭示了 HTTP PATCH 和 JSONPATCH 的一致性
            【解决方案12】:

            在结束关于幂等性的讨论之前,我应该指出,可以通过两种方式在 REST 上下文中定义幂等性。让我们首先将一些事情形式化:

            一种资源是一个函数,其共同域是字符串类。换句话说,资源是 String × Any 的子集,其中所有键都是唯一的。我们将资源类称为Res

            对资源的 REST 操作是一个函数f(x: Res, y: Res): Res。 REST 操作的两个示例是:

            • PUT(x: Res, y: Res): Res = x,和
            • PATCH(x: Res, y: Res): Res,类似于PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}

            (此定义专门用于争论PUTPOST,例如在GETPOST 上没有多大意义,因为它不关心持久性)。

            现在,通过修复 x: Res(从信息上讲,使用柯里化),PUT(x: Res)PATCH(x: Res) 是类型为 Res → Res 的单变量函数。

            1. 调用函数g: Res → Res全局幂等,当g ○ g == g时,即对于任何y: Resg(g(y)) = g(y)

            2. x: Res一个资源,和k = x.keys。调用函数g = f(x)左幂等,对于每个y: Res,我们都有g(g(y))|ₖ == g(y)|ₖ。这基本上意味着如果我们查看应用的键,结果应该是相同的。

              所以,PATCH(x) 不是全局幂等的,而是左幂等的。左幂等性在这里很重要:如果我们修补资源的几个键,我们希望这些键在我们再次修补时是相同的,我们不关心资源的其余部分。

              当 RFC 谈论 PATCH 不是幂等时,它是在谈论全局幂等。好吧,它不是全局幂等的,否则它会是一个失败的操作。


              现在,Jason Hoetger's answer 试图证明 PATCH 甚至不是幂等的,但它破坏了太多东西来这样做:

              • 首先,PATCH 用于集合,尽管 PATCH 被定义为适用于映射/字典/键值对象。
              • 如果有人真的想将 PATCH 应用于集合,那么应该使用一个自然的翻译:t: Set<T> → Map<T, Boolean>,用x in A iff t(A)(x) == True 定义。使用这个定义,补丁是左幂等的。
              • 在示例中,未使用此转换,而是 PATCH 像 POST 一样工作。首先,为什么要为对象生成一个ID?它是什么时候产生的?如果首先将对象与集合的元素进行比较,如果没有找到匹配的对象,则生成 ID,然后程序将再次以不同的方式工作({id: 1, email: "me@site.example"} 必须与 {email: "me@site.example"} 匹配,否则程序总是坏了,PATCH 不可能打补丁)。如果 ID 在检查集合之前生成,则程序再次被破坏。

              可以举例说明 PUT 是非幂等的,破坏了此示例中破坏的一半内容:

              • 一个例子生成附加功能将进行版本控制。人们可能会记录单个对象的更改次数。在这种情况下,PUT 不是幂等的:PUT /user/12 {email: "me@site.example"}第一次导致{email: "...", version: 1},第二次导致{email: "...", version: 2}
              • 与 ID 混淆,每次更新对象时都可能生成一个新的 ID,从而导致非幂等 PUT。

              以上所有例子都是人们可能遇到的自然例子。


              我的最后一点是,PATCH 不应该是全局幂等,否则不会给你想要的效果。您想要更改用户的电子邮件地址,而不触及其余信息,并且您不想覆盖访问同一资源的另一方所做的更改。

            【讨论】:

              【解决方案13】:

              这里有一个很好的解释-

              https://blog.segunolalive.com/posts/restful-api-design-%E2%80%94-put-vs-patch/#:~:text=RFC%205789,not%20required%20to%20be%20idempotent

              正常有效载荷- // House on plot 1 { address: 'plot 1', owner: 'segun', type: 'duplex', color: 'green', rooms: '5', kitchens: '1', windows: 20 } PUT更新- // PUT request payload to update windows of House on plot 1 { address: 'plot 1', owner: 'segun', type: 'duplex', color: 'green', rooms: '5', kitchens: '1', windows: 21 } Note: In above payload we are trying to update windows from 20 to 21.

              现在查看 PATH 有效载荷- // 修补请求负载以更新房屋上的窗口 { 窗户:21 }

              由于 PATCH 不是幂等的,失败的请求不会在网络上自动重新尝试。此外,如果对不存在的 url 发出 PATCH 请求,例如尝试替换不存在的建筑物的前门,它应该简单地失败而不创建新资源,这与 PUT 不同,PUT 会使用有效负载创建一个新资源。想一想,在住宅地址上有一扇孤独的门会很奇怪。

              【讨论】:

                【解决方案14】:

                我将尝试用通俗易懂的方式总结我的理解(也许有帮助)

                补丁不是完全幂等的(在没有人更改实体的另一个字段的理想情况下)。

                在不理想的(现实生活)情况下,有人通过另一个 Patch 操作修改了对象的另一个字段,然后这两个操作都不是幂等的(这意味着您正在修改的资源从任何一个角度来看都是“错误的”)

                所以如果它没有覆盖 100% 的情况,你就不能称之为幂等。 也许这对某些人来说并不重要,但对其他人来说却是

                【讨论】:

                  【解决方案15】:

                  我要补充的一个附加信息是,与 PUT 请求相比,PATCH 请求使用的带宽更少,因为只发送了一部分数据,而不是整个实体。因此,只需使用 PATCH 请求更新特定记录(如(1-3 条记录)),同时使用 PUT 请求更新大量数据。就是这样,不要想太多,也不要太担心。

                  【讨论】:

                    猜你喜欢
                    • 2015-04-12
                    • 2017-02-10
                    • 2020-10-15
                    • 2017-01-05
                    • 2018-01-08
                    • 2019-07-18
                    • 2015-06-15
                    • 2014-08-06
                    相关资源
                    最近更新 更多