【问题标题】:Returning different JSON results for the same request - is this a violation of REST?为同一个请求返回不同的 JSON 结果——这是否违反了 REST?
【发布时间】:2020-01-24 20:26:26
【问题描述】:

请注意 Roy Fielding 关于 REST 设计、指南和原则的以下内容。

5.2.1.1 资源和资源标识符

REST 中信息的关键抽象是资源。任何 可以命名的信息可以是资源:文档或图像, 临时服务(例如“洛杉矶今天的天气”), 其他资源的集合,一个非虚拟对象(例如一个人), 等等。换句话说,任何可能成为目标的概念 作者的超文本参考必须符合 a 的定义 资源。

资源是到一组实体的概念映射,而不是 对应于任何特定点的映射的实体 时间。

更准确地说,资源 R 是一个随时间变化的成员 函数 MR(t),它在时间 t 映射到一组实体或值, 这是等价的。集合中的值可能是资源 表示和/或资源标识符。资源可以映射到 空集,允许在任何之前对概念进行引用 存在这个概念的实现——一个陌生的概念 Web [61] 之前的大多数超文本系统。有些资源是 静态的意思是,当在他们之后的任何时间检查时 创建时,它们总是对应于相同的值集。其他人有一个 随着时间的推移,它们的价值差异很大。

对于资源来说,唯一要求是静态的就是 映射的语义,因为语义是区别 一种资源来自另一种资源。

关键点已加粗,我包含的其余段落是为了上下文。

这是场景。

我有一个具有端点的 Web api:http://www.myfakeapi.com/people

当客户端向此端点发出 GET 请求时,他们会收到一个人员列表。

Person
{
  "Name": "John Doe",
  "Age": "23",
  "Favorite Color": "Green"
}

好的,那很酷。

但是,如果我有一个没有最喜欢的颜色的“人”并且我想像这样返回它们,这是否违反了 REST 设计实践和原则:

Person
{
  "Name": "Bob Doe",
  "Age": "23",
}

或者我应该这样退回它们:

Person
{
  "Name": "Bob Doe",
  "Age": "23",
  "Favorite Color": null
}

问题是请求资源的客户端必须做额外的工作才能首先查看该属性是否存在。有些“人”有最喜欢的颜色,有些则没有。如果 'Favorite Color' 的 json 属性不存在,则忽略它们是否违反 REST 原则 - 还是应该为该属性赋予“null”或空白值?

REST 对此有何看法?我在想我应该返回一个 null 而不是通过 省略 属性来更改客户端请求的资源的表示。

【问题讨论】:

  • 一个 HTML 页面是否因为它不包含表单元素而无效,即使媒体类型声明可能有一些?换句话说,您只能返回您所拥有的信息。您是否包含一些任意的后备值是您的设计选择或您交换消息的媒体类型的详细信息。请注意,我将此问题标记为主要是固执己见,因为对此没有明确的答案,并且基本上归结为个人喜好
  • 一种不同的类比,但真正的问题是 - 如果客户期望一个具有三个属性的 JSON 对象,但有时我决定向他发送相同的对象但具有两个属性,这是 1) 这违反了 REST,并且 2) 不好的做法?我了解添加属性是如何很好的,因为这是一个非破坏性更改,省略属性似乎肯定是一个“破坏性更改”,或者我很困惑?
  • 首先要澄清的是,REST 不是协议,因此您不能违反它。 REST 只是一组约束,如果不正确遵循这些约束,将无法提供 REST 所追求的好处。接下来,客户端会被服务器教授他们需要知道的一切。一个表单,即将教客户端预期的数据元素、服务器期望的表示、发送有效负载的方法以及发送数据的端点。客户在任何时候都不需要查阅外部文档。
  • 如果客户端希望在响应中接收某些字段,它可能认为资源具有certain type,根据Fielding 本人的说法,客户端不应该这样做,因为这只是引入了一个客户端与特定 API 的紧密耦合,不允许同一个客户端轻松与不同的 API 交互

标签: json rest


【解决方案1】:

在我的脑海中,我想不出这违反了任何 REST 约束(如果您有兴趣,这里是指向 brief overview 的链接)。它也不违反 GET 请求的幂等性。但是,这仍然是不好的做法

您的 API 的使用者应该知道会发生什么,理想情况下这应该有很好的文档记录(我很喜欢为此使用 Swagger)。任何预期的变化都应该传达给消费者,可能以发行说明的形式。可能对您的消费者造成破坏的更改应该在您的 API 的新版本中交付。

由于您的 Person1 和 Person2 在技术上是不同的对象结构,因此它本身可能会崩溃(让我们面对现实吧,我们并不总是在开发人员中找到边缘情况)。您不只是希望您的 API 在基本层面上工作并让最终用户陷入困境 - 您希望在设计它时考虑到最终消费者,以便让他们的生活更轻松。

【讨论】:

  • 是的,我说它们是不同的对象结构。如果该引用与您有关,那么也许其他用户的此评论可以为您澄清:stackoverflow.com/a/54903700/1168004
  • 如您所见,intent 不是关于特定对象结构和对象细节的映射,而是对特定资源的映射。也就是我们得到的是哪个人,而不是这个人的结构。上面用户引用的段落与我阅读的版本中的不同,但我认为它更清晰。
  • 对于您的第一条评论 - 抱歉,我误读了您所说的内容。我绝对同意你的观点,它们是不同的对象结构。对于您的第二条评论,文档明确指出 对于资源来说,唯一需要保持静态的是映射的语义,因为语义是一种资源与另一种资源的区别。 我正在考虑“语义”作为对象属性;姓名、喜欢的颜色等。我可以有两双鞋,但如果一个是 13 码,一个是 10 码,它们显然不是同一只鞋。
  • 我明白你在说什么和你的困惑,但这里讨论的语义是映射到资源的语义,而不是资源本身的细节。 object 的语义可能是它的属性,但 mapping 的语义是如何识别资源的:“例如,学术的“作者的首选版本”论文是一个值随时间变化的映射,而“在 X 会议论文集中发表的论文”的映射是静态的。”
  • 哇,感谢您的澄清。我仍然无法相信这不违反 REST。我现在想,尽管我可以放心地将其声明为您之前​​提到的“不良做法”。感谢收听!
【解决方案2】:

我们可以通过多种方式来处理这个问题,具体取决于用例,我将仅列出一个

1) 首选枚举(仅当它对您的用例有意义时)

{
  "Name": "Bob Doe",
  "Age": "23",
  "Favorite Color": NO_COLOR
}

当您一开始就知道属性的值时,定义一组枚举常量,如果该属性不适用于用户,则分配一个默认值。这在几个方面有所帮助:

  • 您的客户知道可能的值是什么,因此他们可以相应地准备他们的客户端系统。

  • 通过提供默认枚举常量,我们传达特定字段的值已成功从持久存储或可能从另一个远程服务检索,但它具有默认值,因为该属性可能不适用于用户此属性没有任何价值。

  • 通过避免 NULL 模式,您的客户端代码将具有弹性,并且客户端可以为默认枚举常量准备其代码。

  • 当您开始为更多用户提供服务时,您可能需要添加更多枚举常量,这些常量可能不适用于您的每个客户。当您添加他们不知道的新枚举时,他们可以轻松地在其解析库中处理此问题,并根据客户端应用程序设计转换为某些内容。在杰克逊,我们可以使用DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL

2) 使用 Null - 不要为所有内容创建枚举常量

在某些情况下,拥有一个 NULL 对象是完全有效的。例如,在下面的示例中,如果没有最喜欢的报价,则使用 null 是有意义的。

{
  "Name": "Bob Doe",
  "Age": "23",
  "Favorite Quote": null
}

3) 清楚地记录您所需的属性

如果您将 swagger 用于您的 REST API 文档,则可以根据需要标记强制属性。未标记的是可选的。这样,客户端将准备好处理它们是否为 NULL 或空字符串。 (它也应该适用于其他 API 文档工具)

不好的做法: 我注意到一些用户以这种方式编码,他们在发送成功响应 200 的相同响应模型中发送错误。请参阅此question 和答案。这绝对是一个不好的做法。不要混合两种不同的响应并将一个属性标记为可选 - 使用状态代码来传达任何问题。我不是在这里谈论部分响应。

4) 添加/修改属性(只要您不违反与客户的合同)

假设稍后添加了 Favorite Color 属性,目前您正在向您的客户发送以下响应。当您添加Favorite Color 时,您将向您的客户发布您的新合同,但您的客户应该有故障安全代码并且他们应该处理未知属性。在杰克逊,我们将为此使用DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES。非破坏性更改不一定需要 v2。

Person
{
  "Name": "Bob Doe",
  "Age": "23",
}

因此,对您的问题的回答是,您应该在设计其余 API 时开始查看前三个选项,您不需要省略任何属性。但是,您可能需要稍后添加一些属性(在 #4 中介绍),这非常好。

【讨论】:

  • 需要明确的是,根据返回的“Person”省略属性并不是“违反 REST”,而只是“不好的做法”对吗?
  • 我什至不会将其称为不好的做法,因为如果情况需要,您可能需要这样做。看看这个answer,它讨论了 API 版本控制。如果您想维护 API 的两个版本,是的,您需要执行与答案类似的操作。我想说,如果您的情况需要,您可以这样做以实现兼容性。但是,如果没有这种情况,而您仍然以这种方式设计应用程序,那么这可能是一个不好的做法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-25
  • 1970-01-01
  • 2017-03-16
  • 2018-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多