从"SOAP"到"REST" 程序员
最近有不少同仁问我,咱们为何要用REST?REST比SOAP好在哪?对于这个问题我想了不下十种答案。但转念一想,如何以一种最直接,相似于武侠小说中"一剑封喉"般的方式,"稳准狠"的解答他们的问题。就不至于就此展开一场"辩论赛"或是"科普贴"。好在问我这个问题的同仁大都不是程序猿界的"小鲜肉",你们对于时下软件研发的基本理念仍是有共同认知的,基于此,我对这个问题的标准答案就是:"REST更OO"。架构
若是您还有兴趣深刻了解REST和SOAP的区别,我以为仍是自行Google吧。spa
这篇文章想给你们介绍的重点更偏重于:咱们如何从"SOAP"转向"REST",以及什么样的"RESTful API"才是真正的REST。设计
虽不赘述SOAP和REST的概念,但仍是简单介绍一下何为SOAP,何为REST:code
SOAP(Simple Object Access Protocol):简单对象访问协议对象
REST(Rerepresentational State Transfer):表示性状态转移接口
你在网上能搜到的异同点大体分为主要三个方面:进程
其实SOAP和REST严格来讲不是两个对等的概念,咱们姑且理解成两种服务设计思想及其具体的实现架构。资源
若是说SOAP和REST不够具象,那我来找两位代言人,自行感觉一下咱们所谓的"SOAP风格"和"REST风格"。it
Web Service或是WCF你们想必不陌生,而Web Service和WCF就是利用SOAP协议进行的服务实现(固然他们也支持REST,但很鸡肋,不足以代言)。咱们要对外提供服务离不开相似的技术。那咱们是如何设计这样的服务接口的呢。提及来真是轻车熟路:咱们先定义对象,然后咱们设计接口。更有甚者,咱们直接定义服务接口。因此一说到Web Service或是WCF咱们就会想到接口,咱们一看到接口,咱们就很是的舒服。那咱们为何舒服,由于这很后台。进程间的接口调用方式老是会给程序员们以莫名的亲切感。
熟悉Web API风格的朋友可能就能想到REST。可Web API的服务应该怎么设计?若是咱们还用上面说到设计Web Service/WCF的"SOAP风格"进行服务的设计,那REST比SOAP还有什么优点?仅仅是更轻量级的优点?
咱们来举个例子:一只喵儿饿了,他要获取食物,食物分别有鱼和熊掌。因此咱们要提供一个服务是食物的获取。基于这个简单的小例子,咱们来看看从"SOAP"到"REST"的转型之路。
第一阶段:
咱们提供一个为喵儿提供食物的REST服务。接到任务咱们开始进行业务分析,最后肯定2个方法:吃鱼和吃熊掌,对!就是这样一个简单粗暴的方法,咱们想让喵儿调用这些方法,调用EatingFish能够得到鱼,调用EatingBearpaw能够得到熊掌。
咱们的思路是:定义对象 --> 定义方法 改写成RESTful风格
很快RESTful API 被定义好了:Get http://service-root/EatingFish和Get http://service-root/EatingBearpaw
喵儿只要用GET请求这些URI就能获得食物。
然而这一版设计很快宣告失败。并非咱们对服务的功能定义出了问题,而是问题出在这个服务"很不RESTful",缘由是:没有站在资源的角度考虑RESTful API的定义,而是延续了"接口"定义的"SOAP风格"。这样从"接口"入手,而后将"接口"以REST的URI形式暴露出来的API仍然是很"SOAP"的,这样定义出的RESTful API,通常是Level 0或Level 1的。
那咱们就来介绍一下REST的4个境界,我也称之为REST的"成熟度模型"。
Level 0:没有明确的资源概念,只有一个URL,只是用单个HTTP方法
Level 1:有明确的资源概念,存在不少URL,只是用单个HTTP方法
Level 2:有明确的资源概念,存在不少URL,使用HTTP做为资源的统一接口
Level 3:在知足2级标准的基础上,使用超媒体做为应用状态的引擎
咱们再来回过头看看咱们的表达Get http://service-root/EatingFish,无疑是Level 0。
即使是咱们生硬的在"EatingFish"前将"Food"表达出来也不过是Level1的表达:Get http://service-root/Food/EatingFish
分别参照一下Level 0和Level 1的定义,咱们发现Level0的表达没有"资源"的概念,仍然是提供了一个"行为"的"接口",这种作法像极了"SOAP"。咱们再来看看Level 1的表达,虽然已经明确出了"Food"这个资源,Food资源下也能够定义多个针对Food的方法,但仍然定义的是诸如"EatingFish"和"EatingBearPaw"这样的一个个行为来做为资源的方法。
第二阶段:
咱们从新思考,要站在"资源"的角度考虑这个服务,而资源的CRUD又能够经过HTTP的POST、GET、PUT和DELETE请求表达。也就是说咱们只要把"资源"表述清楚,利用REST的理念CRUD咱们就没必要考虑了。基于这样的考虑,咱们赶快调整思路:定义资源 --表述资源
资源定义:
咱们定义了三个资源"Food"做为食物的集合,而"Food"下,有"Fish"和"BearPaw",对于资源的表述咱们采起了Collection+Json的超媒体格式。
资源表述:
{
"collection": {
"version": "1.0",
"href": "http://service-root/Food",
"links": [ ],
"items": [
{
"href": "/Fish",
"data": [
{
"name": "Name",
"value": "鱼"
},
{
"name": "Code",
"value": "Fish"
}
],
"links": [ ]
},
{
"href": "/ BearPaw ",
"data": [
{
"name": "Name",
"value": "熊掌"
},
{
"name": "Code",
"value": " BearPaw "
}
],
"links": [ ]
}
],
"queries":[ ],
"template":{
"data": [
{
"name": "Name",
"value": ""
},
{
"name": "Code",
"value": ""
}
]
},
"error": {
"code": "",
"Message": ""
}
}
}
根据资源的定义,经过Collection+Json这种超媒体类型进行资源的表述。使用HTTP协议语义规定的请求类型做为资源的统一接口,不须要再像Level 0 和Level 1中那样单独定义或描述接口。
这时喵儿若是想要吃鱼,只须要GET http://service-root/Food/Fish他就能得到鱼。而若是咱们想要从食物中把熊掌删除掉,也只须要 DELETE http://service-root/Food/BearPaw。若是咱们想帮喵增长一种食物-肉,只须要POST http://service-root/Food/Meat,同时利用资源表述中的模板template,将肉的信息传回去,肉这种食物就被添加进了食物中。
这时咱们发现咱们再去思考服务的设计已经不是站在"要提供什么样的方法"这种基于过程、基于行为的角度去思考。而是基于"资源"的面向对象的设计方法。
作到这个程度,感受已经很是的RESTful了,但回到REST的本意看看,就体会出了问题的所在。回观REST的定义:REST(Rerepresentational State Transfer)表示性状态转移,多读几遍咱们就渐渐的感受到了一个词:"转移"。
而再对照"成熟度模型"咱们发现这时候的API已是Level 2的。Level 2到Level 3,如何表示"转移"成了下一个阶段进阶的关键。
为了更好的展示出第三个阶段的特色,咱们如今扩充一下这个小例子,在喵儿获取过食物以后,他还想来点甜点,甜点有蛋糕和冰激凌。(真是只贪得无厌的喵儿)
第三阶段:
继续调整思路:定义资源 --定义资源的连接关系 --> 资源表述
第一步和第三步与第二阶段没有什么差异,而关键在于第二步,REST的本意但愿可以经过资源的表述,描述出每一个资源与其余资源的连接关系。
回到咱们的例子,咱们定义资源,这时候会有两棵树,"食物"和"甜点":
而接下来,咱们但愿喵儿在得到到食物以后,可以知道接下来有甜点吃。这时咱们引入了资源的状态图:
资源的状态图表述的是从当下的资源可以连接到哪一个资源。咱们能够看到当获取到Fish以后,喵儿就能看到有Desserts,当他访问Desserts时,就能看到为他准备好的Cake和Ice Cream了。
那这种连接如何表述呢?
咱们观察到不论是REST的哪种超媒体格式,都为咱们准备了Link字段:
{
"collection": {
"version": "1.0",
"href": "http://service-root/Food ",
"links": [ ],
"items": [
{
"href":"/Fish",
"data": [
{
"name":"Name",
"value":"鱼"
},
{
"name":"Code",
"value":"Fish"
}
],
"links": [
{
"rel":"Desserts",
"href":" http://service-root/Desserts",
"prompt": "甜点"
}
]
},
{
"href":"/ BearPaw ",
"data": [
{
"name":"Name",
"value":"熊掌"
},
{
"name":"Code",
"value":" BearPaw "
}
],
"links":[
{
"rel":"Desserts",
"href":" http://service-root/Desserts",
"prompt": "甜点"
}
]
}
],
"queries": [ ],
"error": {
"code": "",
"Message": ""
}
}
}
在Level 2 的基础上,利用Collection+Json中对Links的表达,表述了资源间转移的关系。若有必要同时利用Queries表达了对资源集合的过滤。至此咱们完成了从"SOAP"到"REST"的转变。
一些注意:
1、 定义资源不是在定义逻辑模型,更不是E-R;
2、 一个服务中描述的资源不必定是同一根的,"转移"也能够发生在两棵树之间。
3、 一旦出现了不用Level0 和Level 1就表达不了的状况,基本上就是资源的定义出了问题
4、 REST并非"银弹",它解决不了你资源(对象)设计自己的问题。