Richardson 成熟模型

原文地址: http://martinfowler.com/articles/richardsonMaturityModel.html

Richardson 成熟模型

通往REST荣光的步骤

一个将rest做法的基本原则元素分成3个步骤的模型(Leonard Richardson发明的).这些介绍了资源(resources),http动作和超媒体控制(hypermedia controls).



最近我在读<Rest In Practice>(一本我几个同事最近在写的书)的草稿.他们的目的是解释如何用rest网络服务去解决许多企业会遇到的结合问题.这本书的中心思想是一个概念:网络是一个大量大规模的分布系统工作地很好的存在性证明.因此我们可以利用这个思想的一些主意去更简单地建立集合系统.


图一:通往REST的步骤

为了说明一个网络风格的系统的特殊属性,这些作者用了一个Leonard Richardson发明的Rest成熟模型和在一次QCon谈话上的解释.这个模型用简洁的方法去思考利用这三个工具,因此我想尝试一个自己的解释.(这些协议的例子只是用来说明,我不觉得它们值得写成代码并进行测试,因此在细节上会存在许多问题)


等级0

这个模型的起点是用http作为一个远程交互的传输系统,但是不用任何网络的机制.基本上你在这里做的就是把HTTP作为你远程交互系统的通道机制,一般基于远程程序调取(Remote Procedure Invocation).


图表2:等级0的一个例子

让我们假设我希望和我的医生预约一次约定.我的预约软件首先需要知道我的医生在给定日期的开放位置,因此我发起一个请求到医院的约定系统获得这个信息.在等级0的场景中,医院将公开一个服务端点到一些URI上.我接着post到那个端点一个文档包含我的请求的细节.

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 = "!400" end = "1450" >

        <doctor id = "mjones" />

    </slot>

    <slot start = "1600" end = " 1650">

        <doctor id = "mjones" />

    </slot>

</openSlotList>

我的例子用的是XML,但是内容可以是任何格式的:JSON,YAML,键值对或者任何自定义的类型.

我下一步将要定一个约定,我也是post一个文档到那个端点.

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来回传递.如果你用SOAP或者XML-RPC,基本是一样的机制,唯一的区别就是你将XML格式的信件用某种格式的信封封装了起来.


等级1

在RMM中通往Rest的荣光的第一步即是介绍资源.因此现在我们不再让我们的请求到单一的网络端点,而是开始和独立的资源交流.


图表3:加入资源

因此对于我们的初始请求,我们可能有一个给定医生的资源.

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到特定的资源.

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>

不同之处在于如果有任何人需要对预约做任何事,类似于定一些测试,他们首先要get到约定这一个资源,可能通过一个类似于http://royalhope.nhs.uk/slots/1234/appointment的URI,并且post到那个资源.

对于像我这样的对象男士这类似于对象的识别的表达.不是调用方法传递参数,我们提供额外信息的参数去调用关于某个特定对象的方法.


等级2-HTTP动作

我已经在等级0和等级1的所有交互动作用了post动作,但是有些人使用get动作代替或者增加get动作.在那些等级这没有啥区别,它们都是被用作成通道机制运行你通过HTTP进行交互.等级2要驶离这一点了,运用HTTP动作尽可能和HTTP本身运用这些动作一样.


图表4:等级2-加入HTTP动作

对于我们的开发时间列表,这意味着我们希望使用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>

在等级2,在一个类似这个的请求中用get动作是很决断的.HTTP将get定义为了一个很安全的操作,它不会造成显著的改变状态或之类的事情.这允许我们调用get动作,无论是多少次,无论什么顺序,都会返回同样的结果.一个重要的结果就是这允许任何请求的路由可以使用缓存,这是使得网络表现地这么好的关键.HTTP有多种方法来支持缓存,这都能用于所有的通讯中.遵循HTTP的规则使得我们能够利用这种能力的优势.

去定一个约定我们需要能改变状态的HTTP动作,一个post动作或者put动作.我将和之前一样用post动作.

POST /slots/1234 HTTP/1.1

[various other headers]

<appointmentRequest>

    <patient id = "jsmith" />

</appointmentRequest>

运用post还是put的优劣势比我这里能讲到的要更复杂深入,也许我会再写一篇文章谈这一点.但是我想指出一些人没能正确理清POST/PUT和create/update之前的对应关系.他们之前的区别和那不同.

尽管我在等级1运用了同样的post,那里还有一个显著的区别关于远程服务是如何回应的,如果一切顺利,服务器将返回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位置特性,因此客户可以利用它去GET未来资源现在的状态.这个回复同时也含有一个那资源的表现,可以替用户节省一个额外的请求.

当时期出现错误时,这里还有个不同,比如另一个人在定这个会话.

HTTP/1.1 409 Conflict

[various headers]

<openSlotList>

    <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>

</openSlotList>

这个回复重要的一部分是用HTTP状态码预示有些事出错了.比如是409是不错的选择去预示有人已经很矛盾地更新了这个资源.与其用200的返回码却包含一个错误信息,在等级2我们曾经明确地运用过这类的错误返回.这取决于协议设计者去决定用什么状态码,但是如果出现了错误不应该是2xx系列的回复.等级2介绍了HTTP动作和HTTP状态码.

这里有个不一致的前行.REST贡献者谈到了用所有的HTTP动作.他们也在证明他们说的REST试图学习网络的部分成功.但是全世界范围的网络在实践中不怎么使用PUT和POST结构.这有许多理由去多用PUT和PATCH,这里证明现有的网络不是其中之一.

被现在网络的存在性所支持的关键元素是在安全元素和非安全元素间的分离.结合在一起就是用状态码去帮助交流遇到的错误.


等级3-超媒体控制

最后一个等级将解释一些你经常听到在丑陋的首字母缩写词HATEOAS(超文本作为应用状态)下面的一些事情.它强调了如何从得到开放时间列表到知道如何预约的问题.


图表5:等级3-加入超媒体控制

我们从和我们在等级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我们的预约请求,回复中的超媒体控制告诉了我们如何去做.

从等级2拷贝下来的post请求:

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"/>

    <ink 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而不是始终用初始的入口.

一个更远的受益处在于它帮助了客户端的开发者探索协议.这些链接给了客户端的开发者一个下一步可能需要做什么的提示.它没有给所有的信息:"latest"和"cancel"控制都指向同一个URI-他们需要弄清楚其中一个是Get动作另一个是Delete动作.但是这至少给了他们一个起点去思考更多的信息和去协议文档看类似的URI.

类似的,它允许了服务端组去提倡新的能力通过放一个链接到回复中去.如果客户端的开发者关注这些未知的新链接,这些链接会触发他们未来新的探索.

这里没有绝对的标准如何表示超媒体控制.我这里做的只是用了<<Rest In Practice>>组目前的建议,他们服从了ATOM(RFC 4287).我用了一个包含为了特定URI的uri属性和描述关系的ref属性的link元素.一个广为人知的关系是赤裸的(比如self用了指向元素自己),任何为那个服务的明确都是一个完全合格的URI.ATOM状态定义了很有名的linkref是一种链接关系的注册.我写的这些受到ATOM可以做什么的限制,这在等级3restfuless中被看作成一个领导者.


这些等级的意义

我本应强调RMM尽管是一个很好的方式去思考REST的元素,却不是一种REST本身的等级定义.Roy Fielding在<<level 3 RMM is a pre-condition of REST>>中说的很清楚了.像在软件业的许多团队一样,REST有许多定义.但是因为Roy Fielding创造了这个术语,他的定义应该比其他的更重要一些.

我发现RMM有用之处在于其提供了一个一步步的方法去理解在restful思考背后的基本想法.从这点来说,我把它看作一个帮助我们学习概念的工具,而不是我们应该在任务机制中必须用的东西.我不确定目前我们有了足够的例子来确定restful做法是结合系统的正确做法.我真心觉得其是一种很有吸引力的做法以及我会在很多情况下推荐这种做法.

和Ian Robinson谈起这点,他强调当Leonard Richardson第一次提出这个模型时他发现非常有吸引力之处在于他和普遍的设计技术的联系.

- 等级1 利用分治去解决了处理复杂性的问题,将大规模的服务终点打碎成多个资源.

- 等级2 引入了标准的动作集合,因此我们可以用同样的方法处理类似的问题,移除不必要的改变.

- 等级3 引入了可发现性,提供了一种使得协议自己就是文档的方法.

结果是一种模型帮助我们思考我们想要提供的HTTP服务的种类和框起人们对和其交互的期望.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,406评论 0 19
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,517评论 0 6
  • 从今天开始,我开始学习Retrofit,整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解...
    隔壁老李头阅读 6,088评论 4 46
  • 我只是想记录一下生活。 大一第二学期了,我以前总以为上了大学会比较轻松,事实却告诉你,没有什么事很轻松。 今天和舍...
    木穰阅读 195评论 1 1