最近我一直在阅读“Rest实践”的草稿:一本几位同事一直在努力编写的书。 他们的目的是解释如何使用Restful Web服务来处理企业面临的许多集成问题。 这本书的核心在于这样一种观点,Web以一个有效的可扩展分布式系统存在,它的工作效果非常好,这足以证明我们可以从中得到想法轻松地构建集成系统。
图1: REST的步骤
为了帮助解释一个web-style的系统的具体属性,作者使用了由Leonard Richardson开发的一种restful成熟度模型,这模型他在QCon谈话中解释过。 这个模型是理解使用这些技术的好方法,所以我想我会用自己的语言来解释它。 (这里的协议示例只是说明性的,我觉得不值得编码和测试它们,所以细节可能会有问题)
Level 0
该模型的起点是使用HTTP作为远程交互的传输系统,但不使用任何Web的机制。 基本上你在这里做的是使用HTTP作为一般基于远程过程调用的远程交互机制的隧道机制。
图2: Level 0交互例子
假设我想和我的医生预约一下。 我的预约软件首先需要知道我的医生有哪些时间点是可预约的,所以它请求医院预约系统来获取这些信息。 在Level 0的场景中,医院会用URI暴露一个服务端。 然后我将该请求的详细信息发送到该服务端。
POST /appointmentService HTTP/1.1 [various other headers] <openSlotRequest date = "2010-01-04" doctor = "mjones"/>
然后,服务端将返回给我这些信息的文档
HTTP/1.1 200 OK [various headers] <openSlotList> <slot start = "1400" end = "1450"> <doctor id = "mjones"/> </slot> <slot start = "1600" end = "1650"> <doctor id = "mjones"/> </slot> </openSlotList>
我的例子使用XML,但实际上内容可以是任何格式:JSON,YAML,键值对或任何自定义格式。
我的下一步是预约看医生,我可以通过将文档发送到服务端来再次执行。
POST /appointmentService HTTP/1.1 [various other headers] <appointmentRequest> <slot doctor = "mjones" start = "1400" end = "1450"/> <patient id = "jsmith"/> </appointmentRequest>
如果一切顺利,我得到一个答复说我的预约成功。
HTTP/1.1 200 OK [various headers] <appointment> <slot doctor = "mjones" start = "1400" end = "1450"/> <patient id = "jsmith"/> </appointment>
如果有问题,说别人已预约,那么我会在回复主体中收到一些错误信息。
HTTP/1.1 200 OK [various headers] <appointmentRequestFailure> <slot doctor = "mjones" start = "1400" end = "1450"/> <patient id = "jsmith"/> <reason>Slot not available</reason> </appointmentRequestFailure>
到目前为止,这是一个简单的RPC风格系统。 这很简单,因为它只反复发送和接收旧格式XML(POX)。 如果您使用SOAP或XML-RPC,它基本上是相同的机制,唯一的区别是将XML消息包装在某种信封(envelope)中。
Level 1 - 资源
RMM模型“Rest荣耀”第一步是引入资源概念。 所以不是将我们的所有请求都发送给一个单一的服务端,我们现在开始讨论个体资源。
图3: Level 1资源
所以上面的询问请求,我们可能有一个指定医生的资源。
POST /doctors/mjones HTTP/1.1 [various other headers] <openSlotRequest date = "2010-01-04"/>
答复带有相同的基本信息,但每个时间段现在是可以单独寻址(查询)的资源。
HTTP/1.1 200 OK [various headers] <openSlotList> <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/> <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/> </openSlotList>
通过具体资源预约,即可预约具体时间段看医生。
POST /slots/1234 HTTP/1.1 [various other headers] <appointmentRequest> <patient id = "jsmith"/> </appointmentRequest>
如果一切顺利,我会得到类似的回复。
HTTP/1.1 200 OK [various headers] <appointment> <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/> <patient id = "jsmith"/> </appointment>
这与传统RPC区别是,如果任何人需要做任何关于预约看医生的事情,比如做测试,他们首先要得到这个预约资源,可能会是像http://royalhope.nhs.uk/slots/1234/appointment的URI ,提交请求到这URI就可预订该资源。
像我这样熟悉面向对象的人,这就像对象身份的概念。 相当于在以太网调用函数并传递参数,我们在一个特定对象上调用一个方法为其他信息提供参数。
Level 2 - HTTP动词
在Level 0和Level 1我使用HTTP POST动词做交互,但有些人使用GETs或其它。 在这些级别上,它们没有太大的区别,它们都被用作隧道机制,允许您通过HTTP隧道传输交互。 Level 2更进一步,使用HTTP动词尽可能接近它们在HTTP协议中的使用语义。
图4: Level 2HTTP动词
对于读取我们的时间段列表,这意味着我们要使用GET。
GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1 Host: royalhope.nhs.uk
答复与POST一样
HTTP/1.1 200 OK
[various headers]
<openSlotList>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>
在Level 2,使用GET这样的请求至关重要。 HTTP将GET定义为安全操作,即对任何状态都没有任何重大更改。 这允许我们以任何顺序安全地调用GETs次数,并且每次获得相同的结果。 这样做的一个重要结果是它允许任何参与者路由请求可以使用缓存,这是web有良好性能的一个关键因素。 HTTP包括支持缓存的各种措施,可以由通信中的所有参与者使用。 通过遵循HTTP的规则,我们可以利用该功能。
要预约看医生,我们需要一个HTTP动词来改变状态,一个POST或一个PUT。 我会使用我之前用过的POST。
POST /slots/1234 HTTP/1.1 [various other headers] <appointmentRequest> <patient id = "jsmith"/> </appointmentRequest>
使用POST和PUT之间的取舍超出本文范围,也许我会有一天单独撰写一篇文章。 但是我想指出,有些人错误地在POST / PUT和创建/更新之间建立了对应关系。 它们之间的选择与此有所不同。
即使我使用与Level 1相同的提交请求,远程服务的响应方式也有重大差异。 如果一切顺利,服务将回复201的返回码,以表明世界上有一个新的资源。
HTTP/1.1 201 Created
Location: slots/1234/appointment
[various headers]
<appointment>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>
201响应包括具有位置属性的URI,客户端将来可以使用该属性来获取该资源的当前状态。 这里的响应还包括该资源保存的表示,以便客户端可以额外使用。
如果出现问题,还有其他人预约了该医生,还有另一个返回码区别。
HTTP/1.1 409 Conflict [various headers] <openSlotList> <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/> </openSlotList>
此响应的重要部分是使用HTTP返回码来指示出现问题。在这种情况下,409似乎是一个很好的选择,表明其他人已经以不兼容的方式更新了资源。而不是使用200的返回码,而是包含错误响应,在Level 2,我们明确地使用某种类似的错误响应。由协议设计者决定要使用哪些代码,但如果出现错误,应该有非2xx响应。Level 2引入了HTTP动词和HTTP返回码。
这里有不一致的地方。 REST主张使用所有的HTTP动词。他们也证明了他们的做法,REST正在尝试从web成功实践中学习。但在互联网实践中并没有使用PUT或DELETE。有很多明智理由使用PUT和DELETE,这说明web只是REST学习之一不是全部。
Web成功的关键因素是,安全(例如GET)和非安全操作完全分离,以及使用状态代码来帮助传达您遇到的各种错误。
Level 3 - 超媒体控制
最后介绍您经常听到的HATEOAS(超文本作为应用程序状态引擎)看起来有点丑陋首字母缩写。 它解决了如何从时间段列表中如何预约看医生的问题。
图5: Level 3超媒体控制
我们在Level 2发送的第一个GET
GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1 Host: royalhope.nhs.uk
响应多了一个新的元素
HTTP/1.1 200 OK [various headers] <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 = "/slots/5678"/> </slot> </openSlotList>
现在每个时间段都有一个链接元素,其中包含一个URI来告诉我们如何预约看医生。
超媒体控件的要点是,他们告诉我们下一步可以做什么,以及我们需要操作资源的URI。 而不是我们必须知道在哪里发布我预约医生请求,响应中的超媒体控件告诉我们如何做。
POST会再次重复Level 2请求
POST /slots/1234 HTTP/1.1 [various other headers] <appointmentRequest> <patient id = "jsmith"/> </appointmentRequest>
响应包含一些超媒体控件,用于不同的事情。
HTTP/1.1 201 Created Location: http://royalhope.nhs.uk/slots/1234/appointment [various headers] <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 = "/doctors/mjones/slots?date=20100104@status=open"/> <link rel = "/linkrels/appointment/updateContactInfo" uri = "/patients/jsmith/contactInfo"/> <link rel = "/linkrels/help" uri = "/help/appointment"/> </appointment>
超媒体控件的一个明显好处是允许服务器在不破坏客户端的情况下更改其URI方案。只要客户端查找“addTest”链接URI,那么服务器团队可以处理除初始入口URI之外的所有URI。
另一个好处是它帮助客户端开发人员导向下一步。这些链接为客户端开发人员提供了下一步可能发生的情况。但它没有提供所有信息:“最新”和“取消”控件指向相同的URI - 可是要知道它们一个是GET,另一个是DELETE。但是至少清楚更多后续信息的切入点和从协议文档找到类似URI。
类似地,它允许服务器团队通过在响应中添加新链接来发布新功能。如果客户端开发人员正在关注未知链接,那么这些链接可以进一步导向一个触发功能。
关于如何表示超媒体控件没有绝对的标准。我在这里做的是使用REST实践团队中的当前建议,这是跟随ATOM(RFC 4287),我使用一个<link>元素与目标URI的uri属性和一个rel属性来描述那种关系。一个众所周知的关系(例如自己就是引用元素)应该是公开的,任何特定服务器都是一个完全URI。 ATOM提出一个著名的相关类型定义就是链接关系注册表。我写这些是有限的,这些已定义在ATOM,ATOM通常被认为是Level 3 restfulness的领导者。
分层的含义
我应该强调,RMM模型是用来考虑REST元素很好的方式,而不是REST层次的定义。 Roy Fielding已经明确指出,3级RMM是REST的前提条件。与软件中许多术语一样,REST有很多定义,但是由于Roy Fielding创造了这个术语,他的定义应该比其它的更重。
我认为这RMM模型有用的是,它提供了一个很好的一步一步的方式来理解restful思想背后的基本想法。因此,我认为它是帮助我们了解概念的工具,而不是在某种评估机制中使用的工具。我不认为我们有足够的例子说明采取restful方法整合系统是正确,我认为这是一个非常有吸引力的方法,在大多数情况下我会推荐使用。
谈到这里想起Ian Robinson,他强调,发现Leonard Richardson首先提出这模型里有吸引力的地方的是,公共设计技术中的关系,。
- Level 1通过分割和征服来解决处理复杂性的问题,将大型服务端分解成多个资源。
- Level 2引入了一套标准动词,以同样的方式处理类似的情况,消除不必要的变化。
- Level 3引入可发现性,提供一种方式使协议更加自我记录。
这是一种模型,可以帮助我们考虑我们想要提供的各种HTTP服务,并制订人们想与之进行交互的期望。
注:本文翻译自Martin Fowler-Richardson Maturity Model
翻译版权所有转载注明出处