如何解决REST API 设计:什么是独特的操作或资源
多年前,我创建了 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
,它们都具有不同的返回类型。一个接受 @QueryParam
和 @Consumes
特定媒体类型,而另一个不接受。
但是,根据(当前)@ApiOperation
Swagger 注释,我选择了错误的 API 设计。
HTTP 方法和路径的组合创建一个独特的操作
因此,在我将旧项目升级到当前库版本后,Swagger 模型只包含一个 GET /foo
操作 - 这是随机的,因为它依赖于通过 Java 反射进行的运行时代码内省。
那么,问题是:Foo
资源在不同的表示中实际上是“相同”资源还是不同的资源? Swagger 注释似乎暗示了后者(不同的资源 -> 不同的路径)。
解决方法
那么,问题是:不同表示中的 Foo 资源是有效的“相同”资源还是不同的资源?
Fielding 定义了一个资源:
REST 中信息的关键抽象是资源。 任何可以命名的信息都可以是资源:文档或图像、时间服务(例如“洛杉矶今天的天气”)、其他资源的集合、非虚拟对象(例如一个人),等等。换句话说,任何可能成为作者超文本引用目标的概念都必须符合资源的定义。资源是到一组实体的概念映射,而不是在任何特定时间点对应于映射的实体。
更准确地说,资源 R 是一个随时间变化的隶属函数 MR(t),它在时间 t 映射到一组等效的实体或值。集合中的值可以是资源表示和/或资源标识符。资源可以映射到空集,这允许在该概念的任何实现存在之前对其进行引用——这个概念对于 Web 之前的大多数超文本系统来说是陌生的 [61]。 某些资源是静态的,因为在它们创建后的任何时间进行检查时,它们始终对应于相同的值集。 其他人的价值随时间变化很大。 资源唯一需要静态的是映射的语义,因为语义是区分一种资源与另一种资源的原因。
...
REST 使用资源标识符来标识组件之间交互中涉及的特定资源。 REST 连接器提供了一个通用接口,用于访问和操作资源的值集,而不管成员函数是如何定义的或处理请求的软件类型如何。分配资源标识符的命名机构,使引用资源成为可能,负责随着时间的推移维护映射的语义有效性(即,确保成员函数不会改变)。 (Source)
简而言之,资源是您命名的东西,以便稍后引用它。该资源是数据的容器。该数据可以通过多种方式表示。表示是与创建表示所针对的媒体类型相关的资源数据的具体实例。媒体类型本身定义了具体实例的语法和语义。 IE。 HTML 定义了有效负载中可接受的属性和元素以及这些内容所表达的内容。
应使用 REST shouldn't have typed "resources" meaningful to clients 内容类型协商。在这里,客户端通过 Accept
标头向服务器表达其功能,服务器将选择最适合数据的表示格式。行为良好的服务器只会在建议的媒体类型中进行选择,因为它知道客户端可以处理数据。行为不端的客户端只会忽略标头并发送它想要的任何内容,这最终可能会阻止客户端完全处理有效负载。
REST 是关于将客户端与服务器解耦,并允许服务器端在不破坏客户端的情况下在未来发展。然而,这只有在两者都使用某种间接方式时才有可能。 IE。不是 URI 本身是有效载荷中的相关内容,而是附加到该 URI 的链接关系。对于可遍历集合,链接关系可能类似于 next
、prev
、first
或 last
或诸如 prefetch
之类的东西,只是说明一旦客户端加载了所有其他内容并且当前处于空闲状态,则可能会加载带注释的 URI,因为接下来可能会请求此内容。这种链接关系要么是standardized,要么应该遵循Web Linking中定义的扩展机制。
关于您的实际问题。考虑任意产品ABC1234
。该产品包含一些属性,例如其价格、当前库存商品数量、一些描述该产品的元数据以及什么不是。这些属性可以用 JSON、XML 或 HTML 表示。能够处理这些媒体类型的客户端将能够创建具有相同属性的“对象”,几乎没有任何问题。使用的实际表示格式不应影响资源本身的实际数据。毕竟,表示格式只是一种双方同意的在客户端和服务器之间交换数据的方式,以允许有效载荷的接收者以与发送者最初希望的方式相同的方式处理它。
正如 Fielding 之前提到的,这样的资源可能是静态的,也可能随着时间的推移而变化。对于上面的产品示例,价格可能会随时间变化,但这不会改变实际产品的语义。随着时间的推移,有时需要将资源的相同数据作为其他资源的一部分提供。这完全没问题,这里事情开始变得更有趣了。作为公司合并的一部分,我们的一位客户需要以不同的名称公开他们的所有项目。在他们的情况下,他们选择同时提供两个产品名称一年。根据定义,对于任意 HTTP 客户端,这将是两种不同的资源,即 ABC1234
和 XYZ12345
,即使它们“代表”同一真实产品的数据。他们还可以选择使用(永久)将客户重定向到“新”URI,从而提示客户该产品实际上是相同的。
如果您查看缓存在 HTTP 生态系统中的工作原理,则每个名称(或 URI)的资源概念也很明显。这里 effective request URI 用作缓存键,以便查找请求的 URI 是否已经存在存储的响应。对该 URI 执行的任何不安全操作都将导致该存储的响应被逐出。这就是为什么 HTTP 不适合批量操作的原因之一,因为它们可能会绕过缓存并导致错误和/或误导性结果。
多年前,我创建了一个微型网络服务,它以两种表示形式提供相同的资源。
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
并使用不同坐标系的专门媒体类型,或者更好的媒体类型支持 profiles 并通过 profile 属性提示接收者处理器它接收哪种“类型”的数据。如上所述,链接关系还定义了一个 profile
关系名称,可用于允许客户端在返回“公制”或“英制”、“开尔文”、“华氏度”或“摄氏度”的 URI 之间进行选择或类似的测量数字等。
所以,长话短说,粗略地说绝对 URI,包括矩阵、查询和路径参数,是在任意客户端“命名”资源的原因。毕竟,整个 URI 是该资源的标识符。稍微不同的名称可能会导致本地或中间缓存未命中,因此表示不同的资源,即使表达的数据与以前相同。与使用两个略有不同的 URI 重定向指令不同,可以使用同一资源上的内容类型协商或配置文件来“摆脱”仅在返回的表示格式不同的同级“资源”。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。