相关博文:

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

SpringBoot 构建RestFul API 含单元测试

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

什么是Hypermedia?

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

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

HTTP/1.1  OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-
Transfer-Encoding: chunked
Date: Fri, Jun :: GMT {
"tracking_id": "",
"status": "WAIT_PAYMENT",
"items": [
{
"name": "potato",
"quantity":
}
],
"_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的消费者来说有什么好处呢?

不再揣测如何组合使用API

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

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

两周后,我们的API使用权限被对方关闭了,原因是“所有的订单都没有财务信息,无法确认对账”。
“等等,那谁谁谁不是说这个API没用吗?”
“噢,他已经离职了,你们如果要恢复使用,尽快完成这个API的集成吧”
“我。。。”

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

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

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

// 预订后,提示确认订单,那么不熟悉为什么要确认以及不确认的后果的同学就可以想到去问啦
{
"tracking_id": "",
"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": "",
"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": "",
"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": "",
"Hotel": "A ZHAO DAI SUO",
"status": "WAIT_PAYMENT",
"source": "normal",
"_links": {
"coupon": {
"href": "http://zhaodaisuo.com/orders/123456/coupon"
}
}
}
{
"tracking_id": "",
"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

从消费者角度评估RestFul的意义的更多相关文章

  1. SpringBoot 构建RestFul API 含单元测试

    相关博文: 从消费者角度评估RestFul的意义 SpringBoot 构建RestFul API 含单元测试 首先,回顾并详细说明一下在快速入门中使用的  @Controller .  @RestC ...

  2. Java实现多线程生产者消费者模型及优化方案

    生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...

  3. 从互联网进化的角度看AI+时代的巨头竞争

    今天几乎所有的互联网公司在谈论和布局人工智能,收购相关企业.人工智能和AI+成为当今科技领域最灸手可热的名词,关于什么是AI+,其概念就是用以表达将"人工智能"作为当前行业科技化发 ...

  4. 第五次作业——python效能分析与几个问题(个人作业)

    第五次作业--效能分析与几个问题(个人作业) 前言 阅读了大家对于本课程的目标和规划之后,想必很多同学都跃跃欲试,迫不及待想要提高自身实践能力,那么就从第一个个人项目开始吧,题目要求见下. 阅读 阅读 ...

  5. 使用MVVM设计模式构建WPF应用程序

    使用MVVM设计模式构建WPF应用程序 本文是翻译大牛Josh Smith的文章,WPF Apps With The Model-View-ViewModel Design Pattern,译者水平有 ...

  6. MapReduce剖析笔记之八: Map输出数据的处理类MapOutputBuffer分析

    在上一节我们分析了Child子进程启动,处理Map.Reduce任务的主要过程,但对于一些细节没有分析,这一节主要对MapOutputBuffer这个关键类进行分析. MapOutputBuffer顾 ...

  7. 苹果5S指纹扫描识别传感器Touch ID有利于iPhone的安全性

    iPhone5S新增的指纹扫描识别传感器 Touch ID,黑客花了大量的时间表明指纹验证是可以被破解的.即使它可能被黑客攻击,对iPhone5S的安全性而言,仍然具有极大的好处. 为什么一个容易被破 ...

  8. ITU-T Technical Paper: NP, QoS 和 QoE的框架以及它们的区别

    本文翻译自ITU-T的Technical Paper:<How to increase QoS/QoE of IP-based platform(s) to regionally agreed ...

  9. Kafka 0.11.0.0 实现 producer的Exactly-once 语义(中文)

    很高兴地告诉大家,具备新的里程碑意义的功能的Kafka 0.11.x版本(对应 Confluent Platform 3.3)已经release,该版本引入了exactly-once语义,本文阐述的内 ...

随机推荐

  1. 为什么java的main方法必须是静态的

    今天看类型信息时发现一个问题,不能再main方法中打印this关键字的信息,这时想起了之前的知识,不能再静态方法中调用this.理由很简单,this表示“这个对象”,也就是声明一个类的对象,然而静态方 ...

  2. Alpha冲刺-第三天

    1.1 今日完成任务情况以及遇到的问题. 完成任务情况 杜世康:使用正则表达式对于弹幕文本中的数字,字母,符号,非法字符等过滤. 刘丹,李玉莹:实现主播管理功能 曹莹雯,尹楠: 调用NLPIR/ICT ...

  3. CentOS下部署Jupyter

    目录 安装 配置 准备密码密文 生成配置文件 修改配置 启动 参考:在服务器搭建Jupyter notebook 安装 为了环境比较轻,使用pip安装,非Anaconda: # 创建Python虚拟环 ...

  4. ThreadLocal变量

    什么是ThreadLocal变量?ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多.可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副 ...

  5. appium获取Toast内容的方法

    做自动化测试的时候,可能需要根据弹出的Toast提示来做下一步判断.这里记录一下获取Toast内容的方法,同时巩固一下显示等待的方法之一WebDriverWait. from selenium.web ...

  6. LOJ2542 PKUWC2018随机游走(概率期望+容斥原理)

    如果直接dp,状态里肯定要带上已走过的点的集合,感觉上不太好做. 考虑一种对期望的minmax容斥:其中Max(S)为遍历完S集合的期望步数,Min(S)为遍历到S集合中一个点的期望步数.当然才不管怎 ...

  7. POJ 2584 T-Shirt Gumbo

    T-Shirt Gumbo Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3689   Accepted: 1755 Des ...

  8. 在PE32位下安装64位2003、2008系统

    步骤 1.进PE(这里是老毛桃为例) 2.将系统(这里windows2008 r2 为例)拷到本地硬盘 3.将ios镜像出来 4.在PE桌面找到 “windows系统安装”,浏览 系统文件  \sou ...

  9. MT【198】连乘积放缩

    (2018中科大自招最后一题)设$a_1=1,a_{n+1}=\left(1+\dfrac{1}{n}\right)^3(n+a_n)$证明:(1)$a_n=n^3\left(1+\sum\limit ...

  10. 【题解】 bzoj1260: [CQOI2007]涂色paint (区间dp)

    bzoj1260,懒得复制,戳我戳我 Solution: 这种题目我不会做qwq,太菜了 区间打牌(dp) 用f[l][r]表示从l到r最少需要染几次色. 状态转移方程: 1.\(f[l][r]=mi ...