非REST行为的REST API设计

3

我将为设备管理系统设计REST API。

端点:

http://api.example.com/users
http://api.example.com/devices

我希望实现一个端点,可以在选择的设备上执行某些操作。类似这样:
http://api.example.com/devices/1/send_signal 

我知道这不符合REST的兼容性,我正在寻求建议以使其正确。

我在考虑添加另一个端点,例如:

http://api.example.com/calls

当用户向该端点发送带有deviceId参数的POST请求时,将在数据库中创建新条目(以便记录所有调用和调用函数的人),同时在指定设备上调用该函数。这种架构是否很好且符合REST标准?
3个回答

3
您的直觉是正确的。这不是合适的REST。有时可以接受,但大多数情况下,这表明您的领域需要重新设计。
很多时候,有一个领域模型等待被发现。通常,“send_signal”之类的东西告诉您,您已经将API建模得太接近某个库、后端服务或数据库了。毕竟,API是您提供的接口。
正如我之前写过的那样:REST中的R代表资源(这并不正确...等)。
考虑资源。不要考虑过程或调用,也不要考虑内部工具、后端服务或系统架构。那是您自己的东西。API用户只应该关心对API用户有意义的(干净的)抽象。
“/call”和“/.../send_signal”都过于关注过程和内部细节。
你想对设备做什么?你想打开它的相机吗?这将是对 ID 为 1337 的设备上的 Camera 模型进行更新:
PUT /device/1337/camera { power: "on" }

你想要一个设备来压缩一些日志文件并将它们发送到调试服务器?你正在创建一个DebugSession模型:
POST /device/1337/debug_session { delivery_bucket: 42, compress: "bzip" }

你想要一个设备向某个服务器发送消息吗?在设备上创建一个名为 Message 的对象:
POST /device/1337/messages { to: john, body: "Hello World" }

等等,等等。
这就是REST。在REST中,您要仔细地建模您的领域模型。许多REST服务器非常糟糕,因为它们只是一些关系数据库的薄包装,并且存在过多的泄漏抽象问题。许多其他REST服务器非常糟糕,因为它们编写得太接近后端服务、正在运行的作业或其他内部细节。
如果我想启动一个新的服务器,我想说:
POST /server/ { region: eu-1, size: xl, disk: 1MB }

不是:

而是:

POST /resources/blockdisks/create { size: 10GB } => 1337 is created
GET /resources/blockdisks/1337?include_attrs=mountpoint,format
GET /servers/available/map_for/eu-1?xl => DB-Zfaa-dd-12
POST /servers/reserve { id: DB-Zfaa-dd-12, attach: { id: 1337, mountpoint: /dev/sdb2, format: zfs }

“我不是在编造,我曾经要处理过这样的API,它们非常难以使用,而且维护起来更加困难。本文的教训是:第一个暴露了服务器领域模型,只包含对API用户有用的少量属性。第二个则过于紧密地围绕着各种内部工具和系统进行建模。此外,这完全忽略了更重要的REST部分:发现。链接、头信息、重定向等等。但你明确问到了如何命名资源,所以我的答案也是关于这方面的。一旦你有了资源和领域模型的架构,回到白板前重新做一遍:现在包括链接、头信息或其他元数据,以便你的API客户端可以发现他们能做什么以及在哪里可以做到。”

1
虽然我完全同意REST是关于资源和客户端与服务器之间的交互模型,但它也是一种架构,其中服务器通过提供客户端可能需要的所有信息来“服务”客户端以执行其任务。与HTML类似,服务器提供了一个表单,可以将数据放入并发送到服务器,如果需要进一步的输入或配置,则REST API应该向客户端发送表单表示(取决于客户端理解的变体)。这里不需要任何带外信息。 - Roman Vottner
1
接下来,URI 的实际字符并不重要,因为客户端永远不应解释 URI,而是使用一些附带的(有意义的)链接关系名称,这些名称已经 [标准化](https://www.iana.org/assignments/link-relations/link-relations.xhtml)或通过其他手段(例如域名或常见知识,如[schema.org](https://schema.org/))推导出来。这使得服务器可以随时更改其 URI 结构,而不会对尊重链接关系名称的客户端产生负面影响。 - Roman Vottner
1
不应该使用对客户端有意义的类型化资源,而是客户端和服务器应该使用基于明确定义的媒体类型的内容协商。稍后更改某些字段可能会破坏期望某些字段的可用性或值的客户端。内容协商只允许客户端和服务器通过双方都理解的明确定义的表示进行通信,从而减少互操作性问题。 - Roman Vottner
应用 REST 架构的最终目标实际上是解耦客户端和服务器,使前者能够在改变时更加稳健,同时给后者自由演进。为了让一个“值得被称为 REST 架构”的架构,需要上述所有提到的事情以及更多一些。 (https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven) - Roman Vottner
@RomanVottner,我故意在回答中省略了内容发现(链接)和协商的重要性。这是REST的一个非常重要的部分,可能是最重要的部分。但也是最经常被忽视的一部分。我同意。但对我来说,这超出了这个问题的范围,因为它是关于“与哪些资源进行通信”这个问题。 - berkes

1
http://api.example.com/devices/1/send_signal

我知道这不符合REST兼容性,我正在寻求建议以使其变得正确。
它是REST兼容的。REST不关心您为资源标识符使用的拼写方式。
这将是一个伟大的架构和REST兼容吗?
如果这是您想要的,请考虑如何在网站上实现相同的结果。客户端将在浏览器中打开书签,可能会跟随一些链接,填写表单并提交。这将全部起作用,因为(通用的)浏览器了解HTML的处理规则以及如何在HTTP中管理缓存元数据等。链条中没有任何环节需要客户端组成标识符——它要么使用服务器提供的标识符,要么使用通用的HTML表单处理规则计算一个标识符。
OP明显使用动词来传达意图和程序,这不符合RESTful。
不;这是一个常见的误解。REST架构风格中没有任何关于标识符中人类可读语义的规定。URL缩短器可以正常工作。
这类似于说程序中的变量名永远不应该是动词,因为那不能正确地传达意图——编译器/解释器并不关心。
使用拼写约定并不能使URI更加RESTful。请参见Tilkov。甚至避免使用可预测的标识符可能更好,因为这可以确保消费者读取超媒体表示中提供的标识符,这也是重点

2
“REST 不关心资源标识符的拼写方式。”这是正确的。但是,send_signal 并不是一个拼写错误的资源,它是一个过程、一个动作、一个动词。Signal 是一个资源,sent_signal(注意 T)或者甚至是 Sgnlz。但在这里,作者显然使用了一个动词来传达意图和过程,这是 符合 REST 的原则的。 - berkes
@berkes 我也同意VoiceOfUnreason的观点,正如我在对你的答案发表评论时所指出的那样。德语措辞表明,在没有知识和部分知识之间,后者更加邪恶。不幸的是,这在REST方面也是正确的,因为有许多“建议”和“业务最佳实践”仅仅描绘和倡导了REST的错误形象,比如在URI中避免动词,并且实用主义者似乎盲目地遵循和传播这些准则而不重新考虑他们这样做的影响。 - Roman Vottner
Tilkov的演讲相关部分从17:20开始。有趣的是,在这个SO答案中,他提到“在URI中包含动词是一种设计上的问题”。对于未来的读者,这个SO问题这篇关于Fielding被误解的REST论文的博客文章是相关的。 - OfirD

0

我认为你走在了正确的道路上。根据你关于系统的说明,我会从这个方法开始,并且继续进行。

POST http://api.example.com/devices/123/calls

这将向API发送通话详细信息,然后将其保存到数据存储中,并向适当的子系统或内部库发送事件以呼叫设备。

然后,您可以拥有以下端点来获取通话详细信息。

GET http://api.example.com/devices/123/calls/456 
GET http://api.example.com/devices/123/calls -This would also include query parameters to limit the results in some way, probably by date.

如果您想从所有设备获取电话,则可以使用一些查询参数来限制结果集,再根据日期进行筛选。
GET http://api.example.com/devices/calls 

顺便提一下,如果这是仅由您的应用程序使用的内部API,则RPC风格可能是合适的。但是,遵循HTTP / REST,您将使软件更具可塑性,以便在不使其特定于任何一个功能的情况下以更多种方式使用它。

如果您想了解更多关于REST vs RPC的内容,这是一篇不错的文章。https://cloud.google.com/blog/products/application-development/rest-vs-rpc-what-problems-are-you-trying-to-solve-with-your-apis


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接