从“SOAP”到“REST”

"SOAP""REST"

最近有很多同仁问我,我们为什么要用RESTRESTSOAP好在哪?对于这个问题我想了不下十种答案。但转念一想,如何以一种最直接,类似于武侠小说中"一剑封喉"般的方式,"稳准狠"的解答他们的问题。就不至于就此展开一场"辩论赛"或是"科普贴"。好在问我这个问题的同仁大都不是程序猿界的"小鲜肉",大家对于时下软件研发的基本理念还是有共同认知的,基于此,我对这个问题的标准答案就是:"RESTOO"。

如果您还有兴趣深入了解RESTSOAP的区别,我觉得还是自行Google吧。

这篇文章想给大家介绍的重点更偏重于:我们如何从"SOAP"转向"REST",以及什么样的"RESTful API"才是真正的REST

虽不赘述SOAPREST的概念,但还是简单介绍一下何为SOAP,何为REST

SOAP(Simple Object Access Protocol):简单对象访问协议

REST(Rerepresentational State Transfer):表示性状态转移

你在网上能搜到的异同点大致分为主要三个方面:

  • SOAP是一种协议,而REST是一种规范
  • SOAP支持多种传输协议,而REST目前仅支持HTTP/HTTPS
  • SOAP仅支持XML,而REST支持XMLJson甚至HTML

    其实SOAPREST严格来说不是两个对等的概念,我们姑且理解成两种服务设计思想及其具体的实现架构。

    如果说SOAPREST不够具象,那我来找两位代言人,自行感受一下我们所谓的"SOAP风格"和"REST风格"。

    Web Service或是WCF大家想必不陌生,而Web ServiceWCF就是利用SOAP协议进行的服务实现(当然他们也支持REST,但很鸡肋,不足以代言)。我们要对外提供服务离不开类似的技术。那我们是如何设计这样的服务接口的呢。说起来真是轻车熟路:我们先定义对象,而后我们设计接口。更有甚者,我们直接定义服务接口。所以一说到Web Service或是WCF我们就会想到接口我们一看到接口,我们就非常的舒服。那我们为什么舒服,因为这很后台。进程间的接口调用方式总是会给程序员们以莫名的亲切感。

    熟悉Web API风格的朋友可能就能想到REST。可Web API的服务应该怎么设计?如果我们还用上面说到设计Web Service/WCF的"SOAP风格"进行服务的设计,那RESTSOAP还有什么优势?仅仅是更轻量级的优势?

      我们来举个例子:一只喵儿饿了,他要获取食物,食物分别有鱼和熊掌。所以我们要提供一个服务是食物的获取。基于这个简单的小例子,我们来看看从"SOAP"到"REST"的转型之路

    第一阶段:

    我们提供一个为喵儿提供食物的REST服务。接到任务我们开始进行业务分析,最后确定2个方法:吃鱼和吃熊掌,对!就是这样一个简单粗暴的方法,我们想让喵儿调用这些方法,调用EatingFish可以获得鱼,调用EatingBearpaw可以获得熊掌。

    我们的思路是:定义对象 --定义方法 改写成RESTful风格

    很快RESTful API 被定义好了:Get  http://service-root/EatingFishGet  http://service-root/EatingBearpaw

    喵儿只要用GET请求这些URI就能得到食物。

    然而这一版设计很快宣告失败。并不是我们对服务的功能定义出了问题,而是问题出在这个服务"很不RESTful",原因是:没有站在资源的角度考虑RESTful API的定义,而是延续了"接口"定义的"SOAP风格"。这样从"接口"入手,然后将"接口"以RESTURI形式暴露出来的API仍然是很"SOAP"的,这样定义出的RESTful API,一般是Level 0Level 1的。

    那我们就来介绍一下REST4个境界,我也称之为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 0Level 1的定义,我们发现Level0的表达没有"资源"的概念,仍然是提供了一个"行为"的"接口",这种做法像极了"SOAP"。我们再来看看Level 1的表达,虽然已经明确出了"Food"这个资源,Food资源下也可以定义多个针对Food的方法,但仍然定义的是诸如"EatingFish"和"EatingBearPaw"这样的一个个行为来作为资源的方法。

              

    第二阶段:

             我们重新思考,要站在"资源"的角度考虑这个服务,而资源的CRUD又可以通过HTTPPOSTGETPUTDELETE请求表达。也就是说我们只要把"资源"表述清楚,利用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 2Level 3,如何表示"转移"成了下一个阶段进阶的关键。

       

    为了更好的展现出第三个阶段的特点,我们现在扩充一下这个小例子,在喵儿获取过食物之后,他还想来点甜点,甜点有蛋糕和冰激凌。(真是只贪得无厌的喵儿)

       

    第三阶段:

    继续调整思路:定义资源  --定义资源的链接关系 --> 资源表述

    第一步和第三步与第二阶段没有什么差别,而关键在于第二步,REST的本意希望能够通过资源的表述,描述出每个资源与其他资源的链接关系。

    回到我们的例子,我们定义资源,这时候会有两棵树,"食物"和"甜点":

    而接下来,我们希望喵儿在获得到食物之后,能够知道接下来有甜点吃。这时我们引入了资源的状态图:

    资源的状态图表述的是从当下的资源能够链接到哪个资源。我们可以看到当获取到Fish之后,喵儿就能看到有Desserts,当他访问Desserts时,就能看到为他准备好的CakeIce 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并不是"银弹",它解决不了你资源(对象)设计本身的问题。

posted @ 2017-04-20 15:50  zhaozhenqiang  阅读(336)  评论(0编辑  收藏  举报