从消费者角度评估RestFul的意义

相关博文:html

从消费者角度评估RestFul的意义git

SpringBoot 构建RestFul API 含单元测试    github

  REST是目前业界至关火热的术语,彷佛发布的API不带个REST前缀,你都很差意思和别人打招呼了。 然而大部分号称REST的API实际上并无达到Richardson成熟度模型的第三个级别:Hypermedia。 而REST的发明者Roy Fielding博士更是直言“Hypermedia做为应用引擎”是REST的前提, 这不是一个可选项,若是没有Hypermedia,那就不是REST。(摘自Infoq对Fielding博士的第二段访谈)json

什么是Hypermedia?

  那究竟什么是Hypermedia? 采用Hypermedia的API在响应(response)中除了返回资源(resource)自己外,还会额外返回一组连接(link)。 这组连接描述了对于该资源,消费者(consumer)接下来能够作什么以及怎么作。api

举例来讲,假设向API发起一次get请求,获取指定订单的资源表述(representation),那么它应该长得像这样:app

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Jun 2015 02:54:57 GMT

{
    "tracking_id": "123456",
    "status": "WAIT_PAYMENT",
    "items": [
        {
            "name": "potato",
            "quantity": 1
        }
    ],
    "_links": {
        "self": {
            "href": "http://localhost:57900/orders/123456"
        },
        "cancel": {
            "href": "http://localhost:57900/orders/123456"
        },
        "payment": {
            "href": "http://localhost:57900/orders/123456/payments"
        }
    }
}

 

  • 理解连接中的“self”的消费者知道使用get方法访问其“href”的uri能够查看该订单的详细信息
  • 理解连接中的“cancel”的消费者知道使用delete方法访问其“href”的uri能够取消该订单
  • 理解连接中的“payment”的消费者知道使用post方法访问其“href”的uri能够为该订单付款

这看起来颇有趣,然而这对API的消费者来讲有什么好处呢?post

再也不揣测如何组合使用API

  不知道在你的开发生涯中有没有遇到过这样的事情:单元测试

有一天,产品经理跟我说,咱们要和某某酒店集团对接,在线销售它们的酒店,这是他们的联系人和详细的API说明文档。 API说明文档真够详细,有好几十页,凭着丰富的行业经验,我知道我须要找到其中的哪些API来实现基本的业务场景。 几天后,我实现了大部分的API集成,如今能够预订酒店了,订单已经在对方的测试环境生成,大功告成。 等等,这个“添加订单财务信息”的API是干吗的?在和对方的联系人联系后,被告知“没什么用”,好了,真的大功告成了,上线!

两周后,咱们的API使用权限被对方关闭了,缘由是“全部的订单都没有财务信息,没法确认对帐”。  
“等等,那谁谁谁不是说这个API没用吗?”
“噢,他已经离职了,大家若是要恢复使用,尽快完成这个API的集成吧”  
“我。。。”

固然,这里面还有许多别的因素,可是消费者的开发人员每每很难将业务场景和实现业务场景的API联系起来。 他们经常面对是:测试

  • 不熟悉的业务场景
  • 一套对单个API的做用描述详细,但缺少API之间联系的文档。

Hypermedia带来的API自描述特性,使用连接的方式提示接下来作什么和怎么作,正好能够缓解这样的窘境。 若是API服务方能够提供测试环境供消费者测试,那么开发人员能够实际动手探索业务场景的衔接,这时再配合API文档状况就好多了。 回到酒店订单的例子,若是是这样,我可能就不会挨批了:spa

// 预订后,提示确认订单,那么不熟悉为何要确认以及不确认的后果的同窗就能够想到去问啦
{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "WAIT_ACKNOWLEDGED",
    "_links": {
        "self": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "cancel": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "acknowledge": {
            "href": "http://zhaodaisuo.com/orders/123456/payments"
        }
    }
}
// 确认后,提示添加财务信息,不熟悉的同窗就能够问了,然而我真的已经问了呀。。。。
{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "ACKNOWLEDGED",
    "_links": {
        "self": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "billing": {
            "href": "http://zhaodaisuo.com/orders/123456/bill"
        }
    }
}

今后与API版本说再见

  不知道在你的开发生涯中有没有遇到过这样的事情:

有一天,产品经理跟我说,咱们要实现一个新功能blablabla,可是依赖的API版本太老了,这是他们的联系人。
“你好呀,请问咱们须要这个信息,可是如今1.3的版本中没有提供,有什么办法吗?”
“你能够升级到2.1的版本就有了”
“那这个版本是否是向后兼容的啊?咱们用了其中不少接口哦,我不想其它的集成点出问题”
“那固然,放心吧”

结果固然是个悲伤的故事,“你给我过来,我保证不打死你”。 API的发布方也须要增长新功能,API自身也会随着需求变化,因而产生了版本号 

http://www.zhaodaisuo.com/api/v1.2

然而一套API通常会包括多个API,为整套API版本化的粒度太粗了。 一旦消费者但愿得到其中某个API的新特性,他/她只能选择全盘升级并仔细测试或者为每一个集成点配置单独的uri。 这都不够好,而Hypermedia能够改变这种局面。 因为提供了连接来告诉消费者资源的uri,相对“传统”的REST API,uri变成了一种弱耦合, Hypermedia API只须要公布少许入口uri就能够了。好比,以以前酒店订单的例子,只需发布

http://www.zhaodaisuo.com/orders

后续的确认、财务信息的uri是在实际API调用的时候拿到的,无需事先准备。 消费者和发布者之间的强耦合实际上只剩下入口uri和服务契约(解释资源的含义), 当服务契约新增或是发生破坏性的变化时(例如修改了或删除了参数),只须要在资源表述中增长新的连接。

{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "ACKNOWLEDGED",
    "_links": {
        "self": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "billing": {
            "href": "http://zhaodaisuo.com/orders/123456/bill"
        },
        "billing-v1.1": { //billing发生了破坏性变化
            "href": "http://zhaodaisuo.com/orders/123456/bill/v1"
        },
        "coupon": { //新增了优惠券抵用的契约
            "href": "http://zhaodaisuo.com/orders/123456/coupon"
        }
    }
}

这样版本化的粒度就下移到了服务契约的级别,这时消费者就灵活多了,只要按需修改对应的集成点就好了。

完全与API的内部实现解耦

  不知道在你的开发生涯中有没有遇到过这样的事情:

有一天,产品经理跟我说,咱们依赖的一个API发布者通知我,如今判断订单是否可以用优惠券的条件变化,这是他们的联系人。
“你好呀,请问如今订单可否用优惠券的判断条件有什么变化?”
“原来,大家能够经过订单的状态来判断,如今还须要结合订单的来源,参加秒杀活动的订单不能使用优惠券”
“好吧。。。”

因而我在集成代码中作了以下修改:

 if (order.status().equals("WAIT_PAYMENT")) {
    if  (order.source().equals("miaosha")) {
        couponButtonEnabled = false;
    } else {
       //....
    }
} else {
   //...
}

好纠结,就不能把API设计成这样吗?

{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "WAIT_PAYMENT",
    "source": "normal",
    "_links": {
        "coupon": {
            "href": "http://zhaodaisuo.com/orders/123456/coupon"
        }
    }
}
{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "WAIT_PAYMENT",
    "source": "miaosha",
    "_links": {} //秒杀来源的订单不返回含优惠券连接的资源表述。
}

 

 这样客户端代码就简单了,依赖于抽象的业务场景,而不是依赖于具体的实现

if (order.containsLink("coupon")) {
    couponButtonEnabled = true;
} else {
    couponButtonEnabled = false;
}

 

 
转自: http://hippoom.github.io/blogs/value-of-hypermedia-from-client-perspective.html
相关文章
相关标签/搜索