什么是RESTful成熟度模型?
RESTful成熟度模型 即 理查森成熟度模型(简称 RMM),是Leonard Richardson开发的 Restful成熟度模型,该模型首次出现在 QCon 演讲中。当我们尝试设计好的 Web 服务;想区分好的 Web 服务和坏的 Web 服务,并弄清楚 REST 的约束与主观确定的 Web 服务质量之间的关系时,我们需要一种更完整的看待事物的方式,这种方式被称为 Restful成熟度模型 。
![](https://cdn.explinks.com/wp-content/uploads/2024/07/image-322.png)
应该强调的是,RESTful成熟度模型 虽然是思考 REST 元素的好方法,但它本身并不是 REST 级别的定义。Roy Fielding 明确表示,3 级 RMM 是 REST 的先决条件。与软件中的许多术语一样,REST 有很多定义,但由于 Roy Fielding 创造了这个术语,他的定义应该比大多数定义更有分量。
我们发现 RESTful成熟度模型 的用处在于它提供了一个很好的循序渐进的方式来理解 restful 思维背后的基本思想。因此,我们认为它是帮助我们了解概念的工具,而不是某种评估机制中应该使用的东西。我们目前还没有足够的例子来真正确定 restful 方法是集成系统的正确方法。
在与伊恩·罗宾逊谈论此事时,他强调,当伦纳德·理查森首次提出该模型时,他发现该模型有吸引力的地方在于它与常见设计技术的关系。
- 第 1 级使用分而治之的方法来处理复杂性的问题,将大型服务端点分解为多个资源。
- 第 2 级引入了一组标准动词,以便我们以相同的方式处理类似的情况,消除不必要的变化。
- 级别 3 引入了可发现性,提供了一种使协议更具自我文档性的方法。
结果是一个模型,它可以帮助我们思考我们想要提供的 HTTP 服务类型,并定义想要与之交互的人们的期望。
Level 0 级
该模型的起点是使用 HTTP 作为远程交互的传输系统,但不使用任何 Web 机制。本质上,您在这里所做的是使用 HTTP 作为您自己的远程交互机制的隧道机制,通常基于远程过程调用(RPC)。
图 2:0 级交互示例
假设我想预约医生。我的预约软件首先需要知道医生在特定日期有哪些空闲时段,因此它会向医院预约系统发出请求以获取该信息。在 0 级场景中,医院将在某个 URI 上公开服务端点。然后,我将包含我的请求详细信息的文档发布到该端点。POST /appointmentService HTTP/1.1 [各种其他标头] <openSlotRequest date = “2010-01-04” doctor = “mjones”/>
然后服务器将返回一个文档,向我提供以下信息HTTP/1.1 200 OK [各种标头] <openSlotList> <slot start = “1400” end = “1450”> <doctor id = “mjones”/> </slot> <slot start = “1600” end = “1650” > <医生 id = “mjones”/> </slot> </openSlotList>
我在这里使用 XML 作为示例,但内容实际上可以是任何内容:JSON、YAML、键值对或任何自定义格式。
我的下一步是预约,我可以通过将文档发布到端点来再次进行预约。POST /appointmentService HTTP/1.1 [各种其他标头] <appointmentRequest> <slot doctor = “mjones” start = “1400” end = “1450”/> <patient id = “jsmith”/> </appointmentRequest>
如果一切顺利的话,我会收到回复,告知我预约已经完成。HTTP/1.1 200 OK [各种标头] <appointment> <slot doctor = “mjones” start = “1400” end = “1450”/> <patient id = “jsmith”/> </appointment>
如果出现问题,比如说其他人在我之前进入,那么我就会在回复正文中收到某种错误消息。HTTP/1.1 200 OK [各种标头] <appointmentRequestFailure> <slot doctor = “mjones” start = “1400” end = “1450”/> <patient id = “jsmith”/> <reason>Slot 不可用</reason> </appointmentRequestFailure>
到目前为止,这是一个简单的 RPC 样式系统。它很简单,因为它只是来回传递普通的旧 XML (POX)。如果您使用 SOAP 或 XML-RPC,它基本上是相同的机制,唯一的区别是您将 XML 消息包装在某种信封中。
Level 1 级 – 资源
在 RESTful成熟度模型 中实现 Rest 荣耀的第一步是引入资源。因此,现在我们不再将所有请求发送到单个服务端点,而是开始与各个资源对话。
图 3:第 1 级添加资源
因此,通过我们的初步查询,我们可能会获得特定医生的资源。POST /doctors/mjones HTTP/1.1 [各种其他标头] <openSlotRequest date = “2010-01-04″/>
回复带有相同的基本信息,但是每个插槽现在都是可以单独寻址的资源。HTTP/1.1 200 OK [各种标头] <openSlotList> <slot id = “1234” doctor = “mjones” start = “1400” end = “1450”/> <slot id = “5678” doctor = “mjones” start = “1600”结束=“1650”/> </openSlotList>
通过特定资源预约意味着发布到特定的时间段。POST /slots/1234 HTTP/1.1 [各种其他标头] <appointmentRequest> <patient id = “jsmith”/> </appointmentRequest>
如果一切顺利的话,我会收到与之前类似的答复。HTTP/1.1 200 OK [各种标头] <appointment> <slot id = “1234” doctor = “mjones” start = “1400” end = “1450”/> <patient id = “jsmith”/> </appointment>
现在的区别是,如果有人需要对预约做任何事情,比如预约一些测试,他们首先获取预约资源(可能具有类似的 URI), http://royalhope.nhs.uk/slots/1234/appointment
然后发布到该资源。
对于像我这样的对象迷来说,这就像对象身份的概念。我们不是调用以太中的某个函数并传递参数,而是调用一个特定对象上的方法,为其他信息提供参数。
Level 2 级——HTTP 动词
我在级别 0 和 1 中的所有交互中都使用了 HTTP POST 动词,但有些人会改用或同时使用 GET。在这些级别上,这并没有太大区别,它们都被用作隧道机制,允许您通过 HTTP 隧道进行交互。RESTful成熟度模型 的级别 2 不再这样做,而是尽可能接近 HTTP 本身的使用方式来使用 HTTP 动词。
图 4:第 2 级添加了 HTTP 动词
对于插槽列表,这意味着我们要使用 GET。GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1 主机:royalhope.nhs.uk
答复与 POST 相同HTTP/1.1 200 OK [各种标头] <openSlotList> <slot id = “1234” doctor = “mjones” start = “1400” end = “1450”/> <slot id = “5678” doctor = “mjones” start = “1600”结束=“1650”/> </openSlotList>
在第 2 级,对于此类请求,使用 GET 至关重要。HTTP 将 GET 定义为安全操作,即它不会对任何事物的状态做出任何重大更改。这使我们能够以任意顺序安全地调用任意次数的 GET,并且每次都获得相同的结果。这样做的一个重要结果是,它允许请求路由中的任何参与者使用缓存,这是使 Web 性能良好的关键要素。HTTP 包括各种支持缓存的措施,通信中的所有参与者都可以使用这些措施。通过遵循 HTTP 规则,我们能够利用该功能。
要预约,我们需要一个可以改变状态的 HTTP 动词,即 POST 或 PUT。我将使用之前使用的相同 POST。POST /slots/1234 HTTP/1.1 [各种其他标头] <appointmentRequest> <patient id = “jsmith”/> </appointmentRequest>
这里我想讨论的是使用 POST 和 PUT 之间的权衡,也许有一天我会写一篇关于它们的文章。但我想指出的是,有些人错误地将 POST/PUT 与创建/更新对应起来。它们之间的选择与此完全不同。
即使我使用与第 1 级相同的帖子,远程服务的响应方式也存在另一个显著差异。如果一切顺利,服务将回复响应代码 201,以表明世界上有新的资源。HTTP/1.1 201 创建 位置:slots/1234/appointment [各种标题] <appointment> <slot id =“1234”doctor =“mjones”start =“1400”end =“1450”/> <patient id =“jsmith”/> </appointment>
201 响应包含一个带有 URI 的位置属性,客户端可以使用该 URI 在将来获取该资源的当前状态。此处的响应还包括该资源的表示,以便现在为客户端节省额外的调用。
如果出现问题,例如其他人预订了会议,则还会有其他区别。HTTP/1.1 409 冲突 [各种标头] <openSlotList> <slot id = “5678” doctor = “mjones” start = “1600” end = “1650”/> </openSlotList>
此响应的重要部分是使用 HTTP 响应代码来指示出现问题。在这种情况下,409 似乎是一个不错的选择,它表示其他人已经以不兼容的方式更新了资源。在级别 2 中,我们明确使用某种错误响应,而不是使用返回代码 200 但包含错误响应。由协议设计者决定使用什么代码,但如果出现错误,应该有一个非 2xx 响应。级别 2 引入了使用 HTTP 动词和 HTTP 响应代码。
这里出现了不一致的情况。REST 的倡导者谈论使用所有 HTTP 动词。他们还通过说 REST 试图从 Web 的实际成功中学习来证明他们的方法是正确的。但万维网在实践中并不经常使用 PUT 或 DELETE。有合理的理由更多地使用 PUT 和 DELETE,但 Web 的存在证明不是其中之一。
网络存在所支持的关键元素是安全(例如 GET)和非安全操作之间的严格分离,以及使用状态代码来帮助传达您遇到的错误类型。
Level 3 级——超媒体控制
RESTful成熟度模型 的最后一个层次是HATEOAS(超媒体控制)这个难听的缩写词。它解决了如何从空位列表到知道如何预约的问题。
图 5:第 3 级添加超媒体控件
我们从在第 2 级中发送的相同的初始 GET 开始GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1 主机:royalhope.nhs.uk
但回应有新的元素HTTP/1.1 200 OK [各种标头] <openSlotList> <slot id = “1234” doctor = “mjones” start = “1400” end = “1450”> <link rel = “/linkrels/slot/book” uri = ” /slots/1234″/> </slot> <slot id = “5678” doctor = “mjones” start = “1600” end = “1650”> <link rel = “/linkrels/slot/book” uri = “/插槽/5678″/> </slot> </openSlotList>
每个时段现在都有一个链接元素,其中包含一个 URI,用于告诉我们如何预约。
超媒体控件的意义在于它们告诉我们下一步可以做什么,以及我们需要操纵的资源的 URI 才能做到这一点。我们不必知道在哪里发布预约请求,响应中的超媒体控件会告诉我们如何做。
POST 将再次复制 2 级POST /slots/1234 HTTP/1.1 [各种其他标头] <appointmentRequest> <patient id = “jsmith”/> </appointmentRequest>
答复包含许多超媒体控件,用于控制接下来要执行的不同操作。HTTP/1.1 201 创建 位置:http://royalhope.nhs.uk/slots/1234/appointment [各种标题] <appointment> <slot id =“1234”doctor =“mjones”start =“1400”end =“1450”/> <patient id =“jsmith”/> <link rel =“/linkrels/appointment/cancel” uri =“/slots/1234/appointment”/> <link rel =“/linkrels/appointment/addTest” uri =“/slots/1234/appointment/tests”/> <link rel =“self” uri =“/slots/1234/appointment”/> <link rel =“/linkrels/appointment/changeTime” uri = “/医生/mjones/slots?日期=20100104&status=open”/> <link rel =“/linkrels/appointment/updateContactInfo” uri =“/患者/jsmith/contactInfo”/> <link rel =“/linkrels/help” uri =“/help/appointment”/> </appointment>
超媒体控件的一个明显好处是,它允许服务器更改其 URI 方案而不会破坏客户端。只要客户端查找“addTest”链接 URI,服务器团队就可以处理除初始入口点之外的所有 URI。
另一个好处是它可以帮助客户端开发人员探索协议。这些链接为客户端开发人员提供了下一步可能的操作的提示。它并没有提供所有的信息:“self”和“cancel”控件都指向同一个 URI – 他们需要弄清楚一个是 GET,另一个是 DELETE。但至少它为他们提供了一个起点,让他们知道要考虑什么才能获得更多信息,并在协议文档中寻找类似的 URI。
同样,它允许服务器团队通过在响应中添加新链接来宣传新功能。如果客户端开发人员密切关注未知链接,这些链接可能会引发进一步的探索。
对于如何表示超媒体控件,没有绝对的标准。我在这里所做的是使用 REST 实践团队的当前建议,即遵循 ATOM(RFC 4287)。我使用一个<link>
元素,该元素具有一个uri
目标 URI 的属性和一个rel
用于描述关系类型的属性。众所周知的关系(例如self
对元素本身的引用)是裸的,任何特定于该服务器的关系都是完全限定的 URI。ATOM 指出,众所周知的链接关系的定义是链接关系注册表。在我写这些时,这些仅限于 ATOM 所做的事情,ATOM 通常被视为 3 级 restfulness 的领导者。