REST API如何适合基于命令/操作的域?


24

本文中,作者声称

有时,需要公开API中固有的非RESTful操作。

然后

如果一个API有太多的动作,则表明它是使用RPC观点设计的,而不是使用RESTful原理设计的,或者所讨论的API自然更适合RPC类型模型。

这也反映了我在其他地方阅读和听到的内容。

但是,我觉得这很令人困惑,我希望对此事有更好的了解。

示例I:通过REST接口关闭VM

我认为,有两种根本不同的方法来对虚拟机关闭建模。每种方式可能会有一些变化,但现在让我们集中讨论最基本的差异。

1.修补资源的状态属性

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

(或者,PUT在子资源上/api/virtualmachines/42/state。)

VM将在后台关闭,并且在稍后的某个时间点(取决于关闭的成功与否)将成功,否则状态可能不会通过“关闭电源”在内部进行更新。

2.在资源的actions属性上进行PUT或POST

PUT /api/virtualmachines/42/actions
Content-Type:application/json  

{ "type": "shutdown" }

结果与第一个示例完全相同。该状态将立即更新为“关闭”,并可能最终更新为“关闭电源”。

两种设计都是RESTful的吗?

哪个设计更好?

示例II:CQRS

如果我们有一个CQRS域,其中包含许多这样的“操作”(又称命令),它们可能潜在地导致多个聚合的更新,或者无法映射到具体资源和子资源上的CRUD操作?

我们是否应该在可能的情况下尝试建模尽可能多的命令,具体取决于在具体资源上创建或更新的具体资源(遵循示例I的第一种方法),其余部分使用“动作端点”?

还是应该将所有命令映射到动作端点(如示例I的第二种方法)?

我们应该在哪里划界线?设计何时变得不那么RESTful?

CQRS模型是否更适合RPC之类的API?

据我了解,根据上面引用的文字。

从我的许多问题中可以看出,我对该主题有些困惑。您能帮我更好地了解它吗?


“创建动作”似乎不是RESTful的,除非执行的动作之后具有自己的资源标识符。否则,通过PATCH或PUT更改“状态”属性更有意义。对于CQR​​S部分,我还没有一个好的答案。
Fabian Schmengler '16

3
@Laiv没错。这是一个学术问题,我想对此事有更深入的了解。
leifbattermann '16

Answers:


19

在第一种情况下(关闭VM),我认为没有OP替代品是RESTful的。当然,如果将Richardson成熟度模型用作衡量标准,则它们都是leve 2 API,因为它们使用资源和动词。

不过,它们都不使用超媒体控件,而且我认为这是将RESTful API设计与RPC 区别开来的唯一REST类型。换句话说,坚持1级和2级,在大多数情况下,您将拥有RPC样式的API。

为了对关闭虚拟机的两种不同方式进行建模,我将虚拟机本身公开为(其中包括)链接的资源:

{
    "links": [{
        "rel": "shut-down",
        "href": "/vms/1234/fdaIX"
    }, {
        "rel": "power-off",
        "href": "/vms/1234/CHTY91"
    }],
    "name": "Ploeh",
    "started": "2016-08-21T12:34:23Z"
}

如果客户端希望关闭PloehVM,则应遵循具有shut-down关系类型的链接。(通常,如RESTful Web Services Cookbook所述,对于关系类型,您将使用IRI或更精细的标识方案,但我选择使示例尽可能简单。)

在这种情况下,几乎没有其他信息可以提供给操作,因此客户端应该简单地针对网址中的URL进行一个空的POST href

POST /vms/1234/fdaIX HTTP/1.1

(由于此请求没有任何内容,因此很容易将此模型建模为GET请求,但是GET请求应该没有明显的副作用,因此POST更正确。)

同样,如果客户端要关闭虚拟机电源,则将遵循该power-off链接。

换句话说,关系类型的链接提供启示指示意图。每种关系类型都有特定的语义含义。这就是我们有时谈论语义网的原因。

为了使示例尽可能清晰,我特意遮盖了每个链接中的URL。当托管服务器接收到传入的请求时,它将知道这fdaIX意味着关闭,并CHTY91意味着关闭电源

通常,我只是将动作编码为URL本身,以便URL为/vms/1234/shut-down/vms/1234/power-off,但是在教学时,这会模糊关系类型(语义)与URL(实现细节)之间的区别。

根据您拥有的客户端,您可以考虑使RESTful URL不可入侵

CQRS

关于CQRS,Greg Young和Udi Dahan同意的几件事情之一是CQRS不是顶级架构。因此,对于将RESTful API制作得像CQRS一样,我会保持谨慎,因为那意味着客户端将成为您体系结构的一部分。

通常,真正的(第3级)RESTful API背后的推动力是,您希望能够在不破坏客户端且无法控制客户端的情况下发展您的API。如果这是您的动力,那么CQRS将不是我的首选。


您是说第一个示例都不使用RESTful,因为它们没有使用超媒体控件吗?但是我什至没有发布任何响应,只有请求URL和正文。
leifbattermann '16

4
@leifbattermann它们不是RESTful的,因为它们使用消息主体来传达意图。显然是RPC。如果您使用链接来获取那些资源,那么为什么您需要通过主体传达意图?
Mark Seemann

那讲得通。您为什么建议POST?动作不是幂等的吗?无论如何,如何告诉客户端使用哪种HTTP方法?
leifbattermann '16

2
@ guillaume31 DELETE对我来说似乎很奇怪,因为关闭虚拟机后,它仍然存在,仅处于“关闭电源”状态(或类似状态)。
leifbattermann '16

1
该资源不必临时反映VM,它可以表示它的执行实例。
guillaume31

6

通过REST接口关闭VM

这实际上是蒂姆·布雷Tim Bray)在2009年提出的一个著名例子。

罗伊·菲尔丁(Roy Fielding)讨论了这个问题,并分享了以下观点

我个人更喜欢将监视状态(例如电源状态)视为不可编辑的系统。

简而言之,您拥有一个信息资源,可以返回监视状态的当前表示形式。该表示可能包括指向请求更改状态的表单的超媒体链接,并且表单具有指向要处理(每个)更改请求的资源的另一个链接。

塞思·拉德(Seth Ladd)对问题的关键见解

我们已经将“跑步”从一个人的简单状态变成了可以创建,更新和谈论的真实名词。

将其带回重新启动计算机。我认为您应该发布到/ vdc / 434 / cluster / 4894 / server / 4343 / reboots发布后,便拥有一个代表重新启动的URI ,并且可以获取它以进行状态更新。通过超链接的魔力,重新启动的表示方式链接到重新启动的服务器。

我认为铸造URI空间很便宜,而URI甚至更便宜。创建一个建模为名词的活动集合,然后发布,发布和删除!

RESTful编程是网络规模的Vogon官僚机构。你怎么做任何事情 REST风格的?为此发明新的文书工作,并将其数字化。

用某种更怪异的语言,您正在做的是定义用于“关闭VM” 的域应用程序协议,并标识需要公开/实现该协议的资源。

看你自己的例子

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

没关系; 您并没有真正将请求本身视为自己的独立信息资源,但是您仍然可以进行管理。

您错过了一点有关变更的表述。

但是,对于PATCH,封闭的实体包含一组指令,这些指令描述了应如何修改当前驻留在源服务器上的资源以产生新版本。

例如,JSON Patch媒体类型格式化说明,就好像您直接修改JSON文档一样

[
    { "op": "replace", "path": "state", "value": "shutting down" }
]

或者,这个想法很接近,但显然不是正确的。 PUT目标URL上资源状态的完全替代,因此您可能不会选择看起来像集合的拼写作为单个实体表示的目标。

POST /api/virtualmachines/42/actions

与我们将操作附加到队列的假设相一致

PUT /api/virtualmachines/42/latestAction

与我们正在对队列中的尾项进行更新的假设相一致;这样做有点奇怪。最不惊奇的原则是建议给每个PUT自己唯一的标识符,而不是将它们全部放在一个地方并同时修改多个资源。

请注意,就我们讨论URI的拼写而言,REST无关紧要;/cc719e3a-c772-48ee-b0e6-09b4e7abbf8b就REST而言,它是完美的URI。与变量名一样,可读性是另外一个问题。使用符合RFC 3986的拼写将使人们更加快乐。

CQRS

如果我们有一个CQRS域,其中包含许多这样的“操作”(又称命令),它们可能潜在地导致多个聚合的更新,或者无法映射到具体资源和子资源上的CRUD操作?

格雷格·扬(Cregs)

CQRS是一种非常简单的模式,可为原本可能不存在的架构提供许多机会。CQRS不是最终的一致性,不是事件,不是消息传递,没有用于读写的分离模型,也不是使用事件源。

当大多数人谈论CQRS时,他们实际上是在将CQRS模式应用于代表应用程序服务边界的对象。

假设您在HTTP / REST上下文中谈论CQRS,那么假设您在后一种上下文中工作似乎很合理,因此让我们开始吧。

令人惊讶的是,这比上一个示例更容易。原因很简单:命令是message

Jim Webber将HTTP描述为1950年代办公室的应用协议。通过接收邮件并将其放入收件箱来完成工作。同样的想法仍然存在-我们得到一份表格的空白副本,用我们知道的细节填写表格,然后进行交付。塔达

我们是否应该在可能的情况下尝试建模尽可能多的命令,具体取决于在具体资源上创建或更新的具体资源(遵循示例I的第一种方法),其余部分使用“动作端点”?

是的,只要“具体资源”是消息,而不是域模型中的实体。

关键思想:您的REST API仍然是一个接口;您应该能够更改基础模型,而无需客户端更改消息。发布新模型时,会发布Web终结点的新版本,这些版本知道如何采用域协议并将其应用于新模型。

CQRS模型是否更适合RPC之类的API?

并非如此-特别是​​Web缓存是“最终一致的读取模型”的一个很好的例子。使每个视图都可独立寻址,每个视图都有自己的缓存规则,从而使您可以免费扩展一堆。完全采用RPC方式进行读取的吸引力相对较小。

对于写入而言,这是一个棘手的问题:将所有命令发送到单个端点或单个端点族的单个处理程序肯定更容易。REST实际上更多地是关于如何找到端点与客户端的通信方式。

将消息视为自己的唯一资源具有以下优点:可以使用PUT,向中介组件提醒消息处理是幂等的事实,以便它们可以参与某些错误处理情况,这是很高兴的事情。(注意:从客户端的角度来看,如果资源具有不同的URI,那么它们就是不同的资源;事实上,它们在原始服务器上可能都具有相同的请求处理程序代码,这是制服所隐藏的实现细节。接口)。

菲尔丁(2008)

我还应注意,以上内容尚未完全实现RESTful,至少我是如何使用该术语的。我所做的只是描述了服务接口,仅此而已。为了使其成为RESTful,我将需要添加超文本来引入和定义服务,描述如何使用表单和/或链接模板执行映射,并提供代码以有用的方式组合可视化。


2

您可以利用5种级别的媒体类型在请求的content-type标头字段中指定命令。

在虚拟机示例中,将遵循这些原则

PUT /api/virtualmachines/42
Content-Type:application/json;domain-model=PowerOnVm

> HTTP/1.1 201 Created
Location: /api/virtualmachines/42/instance

然后

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=ShutDownVm

要么

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=PowerOffVm

参见https://www.infoq.com/articles/rest-api-on-cqrs


请注意,5LMT是建议的解决方案,不受标准支持。我之前看过CQRS文章,并从中学到了很多。
彼得L

1

链接文章中的示例基于以下思想:启动计算机和关闭计算机必须由命令而不是通过建模资源状态的变化来指导。后者几乎就是REST赖以生存和呼吸的东西。更好地对VM进行建模需要查看其真实世界中的对应对象如何工作以及您作为人类如何与之交互。这是漫长的过程,但是我认为它可以很好地洞察进行良好建模所需的思维类型。

有两种方法可以控制台式计算机的电源状态:

  • 电源开关: 立即切断到电源的电流,使整个计算机突然无序地停机。
  • 开/关按钮: 告诉硬件通知软件,外面有什么东西要关闭。该软件将有序关闭,并通知硬件已完成,并且硬件会向电源发出信号,表明它可以进入待机状态。如果电源开关打开,机器正在运行,并且软件处于无法响应关机信号的状态,则除非关闭电源开关,否则系统不会关闭。(VM的行为将完全相同;如果软件忽略了关机信号,则计算机将继续运行,我必须强制其关闭电源。)如果我希望能够重新启动计算机,则必须重新打开电源开关,然后按开/关按钮。(许多计算机可以选择长按电源按钮直接进入待机状态,但是此模型并不是真正需要的。)可以将此按钮视为切换开关,因为按下该按钮会导致不同的行为,具体取决于按下时的状态。如果电源开关已关闭,则按此按钮绝对不会执行任何操作。

对于VM,这两个都可以建模为读/写布尔值:

  • power-更改为时true,除了会注意到已将开关置于此状态外,什么也不会发生。更改false为时,命令VM进入立即关闭状态。为了完整起见,如果写入后该值未更改,则不会发生任何事情。

  • onoff-更改true为时,如果powerfalse,则什么也不会发生,否则命令启动VM。更改false为时,如果powerfalse,则什么也不会发生,否则,将命令VM通知软件进行有序的关闭,然后将其关闭,然后通知VM可以进入关机状态。同样,出于完整性考虑,无更改写入不会执行任何操作。

伴随着所有这些,我们意识到有一种情况是机器的状态不反映开关的状态,而这是在关机期间。 power将仍然存在,true并且onoff将继续存在false,但是处理器仍在运行其关闭状态,为此,我们需要添加其他资源,以便我们可以知道计算机的实际运行状况:

  • running-只读值,用于表示true虚拟机正在运行以及false何时未运行,这是通过向虚拟机管理程序询问其状态来确定的。

这样做的结果是,如果要启动VM,则必须确保已设置poweronoff资源true。(您可以power通过使其自动重置来跳过该步骤,因此,如果将false其设置为,则该步骤将true在虚拟机已被严格停止后生效。这是否是RESTful-pure的事情,这是另一个讨论的话题。)如果希望它有序地关机,则设置onofffalse,稍后再返回查看机器是否真正停止,如果不是,则设置powerfalse

像现实世界一样,在onoff更改为VM之后,false仍然面临着定向启动VM的问题,但仍然running是因为它处于关闭过程中。您如何处理是一项政策决定。


0

两种设计都是RESTful的吗?

因此,如果您想考虑周全,那就不用担心命令了。客户端不会告诉服务器关闭虚拟机。客户端通过更新状态,然后将表示形式重新放置到服务器上来“关闭”(隐喻地说)其资源表示形式的副本。服务器接受该新状态表示,并且作为其副作用,实际上关闭了VM。副作用由服务器决定。

少了

嘿,服务器,这里的客户端,您介意关闭VM吗

还有更多

嘿,服务器,这里的客户端,我将资源VM 42的状态更新为关闭状态,更新了该资源的副本,然后执行您认为适当的操作

作为服务器接受此新状态的副作用,它可以检查其必须实际执行的操作(例如,物理上关闭VM 42),但这对客户端是透明的。客户端不必担心服务器必须采取的任何操作才能与该新状态保持一致

因此,忘记命令。唯一的命令是HTTP中用于状态转移的动词。客户端不知道,也不关心服务器如何将物理VM置于关闭状态。客户端不是向服务器发出命令来实现这一目的,只是说这是新状态,请弄清楚

这样做的强大之处在于,它可以在流控制方面将客户端与服务器分离。如果以后服务器更改了VM的工作方式,则客户端不会在乎。没有要更新的命令端点。在RPC中,如果将API端点从更改为shutdown,则shut-down您已经破坏了所有客户端,因为它们现在不知道要在服务器上调用的命令。

REST与声明式编程相似,在声明式编程中,无需列出一组指令来更改某些内容,而只需声明您希望的方式并让编程环境对其进行说明。


谢谢你的回答。关于将客户端和服务器分离的第二部分与我自己的理解非常吻合。您是否有资源/链接来备份答案的第一部分?如果我使用资源,HTTP方法,超媒体,自描述消息等,究竟打破了什么REST约束?
leifbattermann

使用资源,HTTP方法等没有问题。HTTP毕竟是RESTful协议。出现问题的地方是使用您所说的“动作端点”。在REST中,存在代表概念或事物的资源(例如虚拟机42或我的银行帐户),HTTP动词用于在客户端和服务器之间传输这些资源的状态。这就对了。您不应该尝试通过将非资源终结点与HTTP动词结合使用来创建新命令。因此,“ virtualmachines / 42 / actions”不是资源,也不应该存在于RESTful系统中。
Cormac Mulhall'1

或者换一种说法,客户端不应尝试在服务器上运行命令(除了仅与资源状态转移有关的有限HTTP动词之外)。客户端应该更新其资源副本,然后简单地要求服务器接受此新状态。接受该新状态可能会产生副作用(VM 42物理上已关闭),但这超出了客户端的范围。如果客户端不尝试在服务器上运行命令,则客户端和服务器之间不会通过这些命令进行耦合。
Cormac Mulhall

您可以在资源上运行该命令...您将如何在银行帐户中说“入金”和“取款”?它将CRUD用于非CRUD。
康拉德

最好使用POST /api/virtualmachines/42/shutdown而不是产生任何“副作用”。用户必须可以理解该API,例如,我如何知道DELETE /api/virtualmachines/42将关闭VM?对我的副作用是一个错误,我们应该将API设计为易于理解和自我描述
Konrad,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.