微服务架构已经很流行, 从概念上要解决的问题: 认识一个系统,如何将这个系统划分(系统的分解),识别出组件, 组件之间如何通讯,信息如何交换,使得协同完成一件事情。 同样还要包括,随着系统的演变,组件如何演化。
那么组件(模块或服务)本身的抽象,以及如何暴露,如何和外部通讯,如何数据交换,以及如何交互?这些都需要面对解决的问题, 重读Roy论文,重新认识到Rest对于上面的问题(组件如何暴露,如何通讯,如何交换数据,以及模块的演化)给出一个答案。下面谈谈自己对Rest的理解。
一切皆资源(resource)
Rest是面向资源的架构ROA( resource -oriented architecture),理解resource这个概念非常重要,或者这个是理解Rest的核心。Rest 是一种理念,将现实世界一切抽象为资源,通过资源将现实世界映射到数字世界; 就如同面向对象,将现实世界一切抽象为对象,通过对象将现实世界映射到数字世界。 比如电商里面的一个商品,一个订单,一个快递单号,甚至一条评论都可以抽象为资源;更抽象点概念,比如提供在线打印的服务,每一个打印的请求也是一个资源; 一个调度引擎,每一次调度的请求都一个资源。
在资源为基本的抽象单元世界里,资源有这么些特性:
1. 每一种现实客观事物都可以抽象为资源;资源就是现实世界的抽象的属性的信息实体。
2. 每一种资源都有一个标识, 或者有多个标识, 就是资源的名字;
3. 在给定的上下文,可以通过名字查找到资源本身;( 资源本身的名字,与物理实现分离)
4. 计算是将资源具体化到表现层;
5. 资源本身具有不可变性;
6. 对于不同表现层之间的转换时无损的,之间可以等价转换;
7. 计算结果也是一种资源,也有其标识符。
在web世界里面多有一一对应。 #1 是相同的理念;#2 资源标识就是URI ; #3 通过就是DNS查找到资源; #4 对应动态资源计算就是一个请求,通过计算转换为表现层数据给用户; #5 资源本身逻辑上保持不变; #6 不同表现层是等价的(xml,json); #7 对于计算出来的结果,也是一种资源;
通过客观世界映射到数字世界,那么客观世界中的互操作,如何体现。下面分别介绍,对象如何被访问(URI),对象如何操作(CRUD),以及信息数据如何交换。
URI (统一资源识别符)
资源如何被访问?或者资源如何将资源暴露外部世界? 这个就是URI,这个天生就于Web紧密连接在一起的,被用作资源的名字。
比如对一个手机的描述 http://hostname:80/products/smartphone, 清晰明了这是一个smarphone的产品,同时可以通过URI,找到获取信息本身。(URI与URL往往一致)。这个是被认为理所当然或者已经熟视无睹,但是理所当然的事情都不是这么的简单的。 比对一下,看看书的URI(ISBN)与URI比较, 看看哪一个可读性好一些?
http://hostname/books/爱丽丝梦游仙境
ISBN:7-301-04815-7
URI是资源的名字,就说说名字本身。比如一个资源
http://host/products/smartphone
http://host/products 也可以理解为名字在网络上的namespace。名字很显然是一个名词性质的词语, 试试想想一个动词的名字是什么感觉:); 同样名字与其实现没有关系,下面的名字将概念和实现耦合在一起,不是好的名字。
http://host/books/xxx.asp
http://host/books/xxx.php
同样名字要名副其实,这个是程序员程序员的品味和修炼有关,不在本文范围里面,暂且不讨论。
统一的方法(CRUD)
Rest 将对于资源的操作定义为CRUD(get/post/put/delete),是的,是所有的操作!或许这样说更容易理解一些,相当面向对象里面所有的对象的方法,当且仅当只有CRUD操作。先回顾一下这个四个操作的定义,然后再看如何统一所有操作.
get: 对应读操作,获取资源;语义很清晰,只读应该不期望改变资源本身。
post: 创建一个资源。 因为资源ID一般是服务创建的,服务器创建成果会返回新的ID,这为一个不等幂操作。
put: 更新资源。 这个需要提交一个完整的资源,当然包括ID,才能更新。这个操作经常会和post搞混。 (post就是没有完整信息比如ID,就只能由服务器创建,put是有ID可以提交更新)。 等幂操作。
delete: 删除资源。 等幂操作。
幂等操作(idempotentance),简单理解一个操作(函数映射)多次,和映射一次效果一样。
f(f(x)) = f(x)
operation vs status
如何将所有的操作统一到CRUD上呢? 比如对于一个订单,我们有增删改查,这个很好理解与CRUD一一对应。但是对于此外的,比如取消,锁定一个订单如何对应? 对于传统的OO理念我们会定义一组操作:
class OrderManager{
cancelOrder( int id){...}
lockOrder(int id) {...}
}
对于SOAP,RPC我们经常都是这么做的,暴露出方法。
对于Rest里面我们把操作当成一个状态的修改。对于上面的Cancel方法在rest里面可以这样定义
put http://host/books/xxx
body: { "status": "cancel"}
这样就将操作转换为状态变化,只要提供状态修改的方法,更新操作(put), 就可以统一所有类似的操作。貌似一个很简单的转化就可以将所有的操作统一,还是很是强大。
既然CURD可以完全统一所有对资源的操作,那么很显然CRUD可以借助于HTTP协议的动词来实现。很是完美,谁让Roy T. Fielding就是http协议和Rest概念的提出者。
既然通过URI来访问资源,所有的操作http动词可以来操作。下一个问题就是信息如何交换。
Representation(表现层)
数据是信息的载,而数据本身可以有不同表现形式 如xml,json,yaml,binary。 那么服务之间如何信息的交换,就是通过表现层来交换。先看看传统的rpc,数据如何传输?数据的格式,对象的本身都在开发的时候定义好。 这样使得客户端和服务器都绑定在一起,没有办法单独演化升级。而对于Rest定义客户端和服务器对于数据可以根据客户端的请求来返回(Content -type),同样服务器端可以支持不同的格式。 同样之间还可以协商机制确定如何传递数据。 这样服务器与客户端解耦,可以独立升级, 独立演化,是软件架构追求一致追寻的目标。
Stateless (无状态)
互联网上资源天生就可以任何时间任何地点的可以访问,其访问弹性是不受控制的。如何做到弹性,这个一个挑战. 试试想想一般的程序如果要做到弹性计算,而不用去改代码,不是一般挑战。 无状态很好的适应这样的场景。
在互联网上,客户端浏览器发送URL请求到服务器,服务器接受请求响应返回给客户。 如何客户访问量陡增,比如双11, 服务器就通过扩展来并发处理。如果请求是没有状态的, 就可以被分发到不同的服务器,服务器只需要根据请求的本身去应答,而不去考虑请求的上下文。如果有状态,那么处理起来就复杂很多,服务器需要根据请求查找上下文,如果处理的服务器没有上下文,就需要同步上下文。更进一步,状态更新,如何保证上下文一致。这些带来的复杂度,往往就是bug,影响到开发效率,提高维护成本。 另外,这样上下文相关对于缓存也没有存在同样的问题。
当然天下没有免费的午餐。 没有状态就需要对相应每一次客户端在请求的时候,需要额外的信息来标识自己,cookie或者额外的参数。 很显然rest不是最优的传输效率不是最高的。 比起来带来的可扩展性,好处远远大于那点额外的带宽占用。
HATEOAS
(Hypermedia As The Engine of Application State)
超链接就是一个应用程序状态引擎。这个是Rest对API的约束, 让服务器和客户端进一步交互上解耦。这个和传统的分布式应用交互不太一样。 比如rpc,预先定义一组api,然后客户端只能根据预先定义的api来与服务器之间交互,这样服务器和客户端之间就产生依赖,二者不能独自演化。
Rest API 对于每一个api返回值,不但需要告诉返回哪些值,同样还需要告诉下一步有哪些交互,交互的信息是通过超文本。这样子从一个起始点api就可以根据返回相关的超链接,来进行下一步操作。理想的这样所有的流程都可以通过一个api,根据超文本连接完成。貌似爬虫很喜欢:)
这里有一个例子
下面是个例子,首先是一个GET请求账户的信息
GET /account/12345 HTTP/1.1
Host: somebank.org
Accept: application/xml
将会返回 :
HTTP/1.1 200 OK
Content-Type: application/xml
<account>
<account_number>12345< /account_number>
<balance currency="usd">100.00 </balance>
<link rel="deposit" href="https://somebank.org/account/12345/deposit" / link> <rel="withdraw" href="https://somebank.org/account/12345/withdraw" / link><rel="transfer" href="https://somebank.org/account/12345/transfer" / link> <rel="close" href="https://somebank.org/account/12345/close" />
</account>
这样用户可以通过返回值中的URL,做下一次操作。 这样交互不需预定义API。 碰到的大多数见到的rest API,这一点都不满足,被称为restful.
本文回顾一下Rest 理念(一切皆资源),以及rest api的五大原则(URI,Uniform operation, representation,stateless 和 HATEOAS ) 。
https://stackoverflow.com/questions/152871/isnt-resource-oriented-really-object-oriented
https://www.w3.org/People/Connolly/9703-web-apps-essay.html
http://resources.1060research.com/docs/IntroductionToResourceOrientedComputing-1.pdf
https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm