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

如何解决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 的链接关系。对于可遍历集合,链接关系可能类似于 nextprevfirstlast 或诸如 prefetch 之类的东西,只是说明一旦客户端加载了所有其他内容并且当前处于空闲状态,则可能会加载带注释的 URI,因为接下来可能会请求此内容。这种链接关系要么是standardized,要么应该遵循Web Linking中定义的扩展机制。

关于您的实际问题。考虑任意产品ABC1234。该产品包含一些属性,例如其价格、当前库存商品数量、一些描述该产品的元数据以及什么不是。这些属性可以用 JSON、XML 或 HTML 表示。能够处理这些媒体类型的客户端将能够创建具有相同属性的“对象”,几乎没有任何问题。使用的实际表示格式不应影响资源本身的实际数据。毕竟,表示格式只是一种双方同意的在客户端和服务器之间交换数据的方式,以允许有效载荷的接收者以与发送者最初希望的方式相同的方式处理它。

正如 Fielding 之前提到的,这样的资源可能是静态的,也可能随着时间的推移而变化。对于上面的产品示例,价格可能会随时间变化,但这不会改变实际产品的语义。随着时间的推移,有时需要将资源的相同数据作为其他资源的一部分提供。这完全没问题,这里事情开始变得更有趣了。作为公司合并的一部分,我们的一位客户需要以不同的名称公开他们的所有项目。在他们的情况下,他们选择同时提供两个产品名称一年。根据定义,对于任意 HTTP 客户端,这将是两种不同的资源,即 ABC1234XYZ12345,即使它们“代表”同一真实产品的数据。他们还可以选择使用(永久)将客户重定向到“新”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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-