REST API 设计:什么是独特的操作或资源

REST API design: what is a unique operation or resource

几年前,我创建了 a tiny web service,它以两种表示形式提供相同的资源。

# returns a collection of Foos
GET /foo
# returns the same collection of Foos in a different JSON representation
GET /foo?projection=X with 'Accept: my-specific-media-type'

这在 (Java) 代码中工作得很好,因为我可以将两个方法映射到相同的 @Path,两者都具有不同的 return 类型。一个接受 @QueryParam@Consumes 特定的媒体类型,而另一个不接受。

但是,根据(当前)@ApiOperation Swagger 注释,我选择了错误的 API 设计。

A combination of a HTTP method and a path creates a unique operation

因此,在我将我的旧项目升级到当前库版本后,Swagger 模型仅包含一个 GET /foo 操作 - 这是随机的,因为它取决于通过 Java 反射进行的运行时代码自省。

因此,问题是:Foo 资源 以不同的表示形式 是有效的“相同”资源还是不同的资源? Swagger 注释似乎暗示了后者(不同的资源 -> 不同的路径)。

So, the question is this: is the Foo resource in a different representation effectively the "same" resource or is it a different resource?

Fielding 定义了一个资源:

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on. In other words, any concept that might be the target of an author's hypertext reference must fit within the definition of a resource. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

More precisely, a resource R is a temporally varying membership function MR(t), which for time t maps to a set of entities, or values, which are equivalent. The values in the set may be resource representations and/or resource identifiers. A resource can map to the empty set, which allows references to be made to a concept before any realization of that concept exists -- a notion that was foreign to most hypertext systems prior to the Web [61]. Some resources are static in the sense that, when examined at any time after their creation, they always correspond to the same value set. Others have a high degree of variance in their value over time. The only thing that is required to be static for a resource is the semantics of the mapping, since the semantics is what distinguishes one resource from another.

...

REST uses a resource identifier to identify the particular resource involved in an interaction between components. REST connectors provide a generic interface for accessing and manipulating the value set of a resource, regardless of how the membership function is defined or the type of software that is handling the request. The naming authority that assigned the resource identifier, making it possible to reference the resource, is responsible for maintaining the semantic validity of the mapping over time (i.e., ensuring that the membership function does not change). (Source)

简而言之,资源就是您为以后引用它而命名的东西。此资源是数据的容器。这些数据可以用多种方式表示。表示是相对于 media-type 创建表示的资源数据的具体实例。 media-type 本身定义了具体实例的语法和语义。 IE。 HTML 定义哪些属性和元素在有效负载中是可接受的,以及这些东西表达的内容。

As REST shouldn't have typed "resources" meaningful to clients 应使用内容类型协商。在这里,客户端通过 Accept header 向服务器表达其能力,服务器将选择最适合数据的表示格式。 well-behaved 服务器只会在建议的媒体类型中进行选择,因为它知道客户端可以处理数据。非 well-behaved 客户端将忽略 header 并发送它想要的任何内容,这最终可能会阻止客户端完全处理有效负载。

REST 是关于客户端与服务器的解耦,并允许服务器端在不破坏客户端的情况下在未来发展。然而,这只有在两者都使用某种间接寻址时才有可能。 IE。不是 URI 本身是有效负载中的相关内容,而是附加到该 URI 的 link-relations。 link 关系可能类似于 nextprevfirstlast 对于可遍历的 collection 或类似 prefetch 只是说明一旦客户端加载了所有其他内容并且当前处于空闲状态,可能会加载带注释的 URI 的内容,因为下一次可能会请求此内容。这种 link 关系是 standardized or should follow the extension mechanism defined in Web Linking.

关于你的实际问题。想一想任意产品 ABC1234。该产品包含一些属性,例如它的价格、当前库存商品数量、一些描述该产品的元数据等等。这些属性可以用 JSON、XML 或 HTML 表示。能够处理这些 media-type 的客户端将能够创建具有相同属性的“object”,几乎没有任何问题。实际使用的表示格式不应影响资源本身的实际数据。毕竟,表示格式只是一种双方同意的在客户端和服务器之间交换数据的方式,以允许有效载荷的接收者按照发送者最初打算的方式处理它。

正如菲尔丁之前提到的,这样的资源可能是静态的,也可能会随着时间的推移而变化。对于上面的产品示例,价格可能会随时间变化,但这不会改变实际产品的语义。随着时间的推移,有时某个资源的相同数据需要作为其他资源的一部分提供。这完全没问题,这里的事情开始变得更有趣了。作为公司合并的一部分,我们的一位客户需要用不同的名称公开他们所有的项目。在他们的案例中,他们选择同时提供两个产品名称一年。根据定义,对于任意 HTTP 客户端,这将是两种不同的资源,即 ABC1234XYZ12345,即使它们“代表”相同 real-live 产品的数据。他们还可以选择使用(永久)将客户端重定向到“新”URI,从而提示客户该产品实际上是相同的。

如果您了解缓存在 HTTP 生态系统中的工作原理,那么每个名称(或 URI)资源的概念也会很明显。这里 effective request URI 用作 cache-key 以查找请求的 URI 是否已经存在存储的响应。对该 URI 执行的任何不安全操作都将导致该存储的响应被逐出。这就是为什么 HTTP 不适用于 batch-operations 的原因之一,因为它们可能完全绕过缓存并导致错误的 and/or 误导性结果。

Years ago I created a tiny web service that serves the same resource in two representations.

GET /foo               # returns a collection of Foos
GET /foo?projection=X  # returns a collection of Foos in a different coordinate system i.e.  different representation

根据 HTTP 定义有效请求 URI 的方式,这两个 URI 实际上将针对两个不同的资源,尽管它们只是用不同的表示形式表达相同的数据。一个可能更好的方法是只公开 /foo 并为不同的坐标系使用专门的 media-type 或更好的 media-type 支持 profiles 并提示收件人处理器通过 profile 属性接收它接收的“哪种”数据。 Link 关系,如上所述,还定义了一个 profile 关系名称,可用于允许客户端在 URI 返回“metric”或“imperial”、“Kelvin”、“Fahrenheit”之间进行选择或“摄氏度”或类似的测量数字等。

所以,长话短说,松散地讲绝对 URI,包括矩阵、查询和路径参数,就是在任意客户端“命名”资源的原因。毕竟整个 URI 就是该资源的标识符。轻微y 不同的名称可能会导致本地或中间缓存未命中,因此指示不同的资源,即使表示的数据与以前相同。无需使用两个略有不同的 URI 重定向指令,可以使用内容类型协商或同一资源上的配置文件来“摆脱”仅在返回的不同表示格式方面不同的同级“资源”。

您 运行 遇到的部分问题是 REST 概念和 Swagger/OpenAPI 概念的混合。

Resource 是一个 REST 概念:“任何可能成为作者超文本引用目标的概念都必须符合资源的定义”

Representation 是一个 REST 概念:“表示是字节序列,加上描述这些字节的表示元数据。”

Operations 是一个 OpenAPI 概念:“OpenAPI 将唯一操作定义为路径和 HTTP 方法的组合。”

这里有一定程度的紧张,因为观点实际上并不一致。

例如,从 REST 的角度来看,没有理由记录“GET 操作”,因为 GET 是统一接口的一部分 - 无论将什么值用作目标 URI,它都具有相同的语义.这是使万维网成为可能的关键架构约束的一部分——一致的语义意味着我们可以使用通用组件(如 web 浏览器)与 all 交互 网络上的不同资源。


is the Foo resource in a different representation effectively the "same" resource or is it a different resource?

“视情况而定”。

“一个资源,不同的表示”的一个典型例子是图片,我们可能有 GIF、JPEG、PNG、BMP。相同的图片 (ish),但需要以不同方式处理的不同字节序列。

同样,你可能有一个网页(HTML),还有一个text/plain表示,或JSON表示等

要问的重要问题之一:通用缓存是否会包含 return 请求的“正确”表示所必需的信息?

就是说:鉴于您的原始设计是使用查询参数来区分一个投影与另一个投影,您应该尊重这种直觉并继续将不同的表示视为属于不同的资源(这意味着通用缓存将让它们完全分开)。

这是否意味着您要共享相同的路径 /foo(将 projection 视为可选的 @ApiParam),或者为每个投影提供不同的路径(为每个投影定义单独的操作)每个独特的路径)不太清楚。在棕地项目中,我的偏好是记录您已有的内容,而不是进行大量重大更改。

但将“易于记录”视为设计约束当然是合理的。