分布式系统核心:REST风格的架构,REST成熟度模型及REST API管理

Posted Javachichi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式系统核心:REST风格的架构,REST成熟度模型及REST API管理相关的知识,希望对你有一定的参考价值。

成熟度模型

正如前文所述,正确、完整地使用REST是困难的,关键在于RoyFielding所定义的REST只是一种架构风格,它并不是规范,所以也就缺乏可以直接参考的依据。好在Leonard Richardson补充了这方面的不足。

他提出的关于REST的成熟度模型(Richardson Maturity Model),将REST的实现划分为不同的等级。图8-1展示了不同等级的成熟度模型。
图8-1 REST成熟度模型

第0级:使用HTTP作为传输方式

在第0级中,Web服务只是使用HTTP作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。SOAP和XML-RPC都属于此级别。比如,在一个医院挂号系统中,医院会通过某个URI来暴露出该挂号服务端点(Service Endpoint)。然后患者会向该URI发送一个文档作为请求,文档中包含了请求的所有细节。
在这里插入图片描述

POST /appointmentService HTTP/1.1
[省略了其他头的信息……]
<openSlotRequest date = "2010-01-04" doctor = "mjones"/>

然后服务器会传回一个包含了所需信息的文档。

HTTP/1.1 200 OK
[省略了其他头的信息……]
<openSlotList>
<slot start = "1400" end = "1450">
<doctor id = "mjones"/>
</slot>
<slot start = "1600" end = "1650">
<doctor id = "mjones"/>
</slot>
</openSlotList>

在这个例子中我们使用了XML,但是内容实际上可以是任何格式,比如JSON、YAML、键值对等,或者其他自定义的格式。

有了这些信息,下一步就是创建一个预约。这同样可以通过向某个端点发送一个文档来完成。

POST /appointmentService HTTP/1.1
[省略了其他头的信息……]
<appointmentRequest>
<slot doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointmentRequest>

如果一切正常,那开发者能够收到一个预约成功的响应。

HTTP/1.1 200 OK
[省略了其他头的信息……]<appointment>
<slot doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>

如果发生了问题,比如有人在我前面预约上了,那我会在响应体中收到某条错误信息。

HTTP/1.1 200 OK
[省略了其他头的信息……]
<appointmentRequestFailure>
<slot doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
<reason>Slot not available</reason>
</appointmentRequestFailure>

到目前为止,这都是非常直观的基于RPC风格的系统。它是简单的,因为只有Plain Old XML(POX)在这个过程中被传输。如果你使用SOAP或者XML-RPC,原理也是基本相同的,唯一的不同是——你将XML消息包含在了某种特定的格式中。
在这里插入图片描述

第1级:引入了资源的概念

在第1级中,Web服务引入了“资源”的概念,每个资源有对应的标识符和表达。所以,不是将所有的请求发送到单个服务端点,而是和单独的资源进行交互。

因此在我们的首个请求中,对指定医生会有一个对应的资源。

POST /doctors/mjones HTTP/1.1
[省略了其他头的信息……]
<openSlotRequest date = "2010-01-04"/>

响应会包含一些基本信息,包含了各个时间段的就诊时间信息,这些就诊时间可以被作为资源单独处理。

HTTP/1.1 200 OK[省略了其他头的信息……]
<openSlotList>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>

有了这些资源后,创建一个预约就是向某个特定的就诊时间发送请求。

POST /slots/1234 HTTP/1.1
[省略了其他头的信息……]
<appointmentRequest>
<patient id = "jsmith"/>
</appointmentRequest>

如果一切顺利,则会收到和前面类似的响应:

HTTP/1.1 200 OK
[省略了其他头的信息……]
<appointment>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>

第2级:根据语义使用HTTP动词

在第2级中,Web服务使用不同的HTTP方法来进行不同的操作,并且使用HTTP状态码来表示不同的结果。例如,GET方法用来获取资源,DELETE方法用来删除资源。

在医院挂号系统中,获取医生的就诊时间信息需要使用GET。

GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk

响应和之前使用POST发送请求时一致。

HTTP/1.1 200 OK
[省略了其他头的信息……]
<openSlotList><slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>

像上面那样使用GET来发送一个请求是至关重要的。HTTP将GET定义为一个安全的操作,它并不会对任何事物的状态造成影响。这也就允许我们可以用不同的顺序若干次调用GET请求而每次还能够获取到相同的结果。一个重要的结论就是,GET允许参与到路由中的参与者使用缓存机制,该机制是让目前的Web运转良好的关键因素之一。HTTP包含许多方法来支持缓存,这些方法可以在通信过程中被所有的参与者使用。通过遵守HTTP的规则,我们可以很好地利用该缓存。

为了创建一个预约,我们需要使用一个能够改变状态的请求方式。

这里使用和前面相同的一个POST请求。

POST /slots/1234 HTTP/1.1
[省略了其他头的信息……]
<appointmentRequest>
<patient id = "jsmith"/>
</appointmentRequest>

如果一切顺利,则服务会返回一个201响应来表明新增了一个资源。这是与第1级的POST响应完全不同的。第2级中的操作响应都有统一的返回状态码。

HTTP/1.1 201 Created
Location: slots/1234/appointment
[省略了其他头的信息……]
<appointment>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>

201响应包含了一个Location属性,它是一个URI。将来客户端可以通过GET请求获得该资源的状态。以上的响应还包含该资源的信息,从而省去了一个获取该资源的请求。当出现问题时,第2级和第1级还有一个不同之处。比如某人预约了该时段:

HTTP/1.1 409 Conflict
[various headers]
<openSlotList>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>

在上例中,409表明该资源已经被更新了。与使用200作为响应码再附带一个错误信息相比,在第2级中我们会明确响应码的含义,以及其所对应的响应信息。

第3级:使用HATEOAS

在第3级中,Web服务使用HATEOAS。HATEOAS是Hypertext AsThe Engine Of Application State的缩写,是指在资源的表达中包含了链接信息,客户端可以根据链接来发现可以执行的动作。

从上述REST成熟度模型中可以看到,使用HATEOAS的REST服务是成熟度最高的,也是Roy Fielding所推荐的“超文本驱动”的做法。对于不使用HATEOAS的REST服务,客户端和服务器的实现之间是紧密耦合的。客户端需要根据服务器提供的相关文档来了解所暴露的资源和对应的操作。当服务器发生变化(如修改了资源的URI)时,客户端也需要进行相应的修改。而在使用HATEOAS的REST服务中,客户端可以通过服务器提供的资源的表达来智能地发现可以执行的操作。当服务器发生了变化时,客户端并不需要做出修改,因为资源的URI和其他信息都是被动态发现的。下面是一个HATEOAS的例子。

{
"id": 711,
"manufacturer": "bmw",
"model": "X5",
"seats": 5,"drivers": [
{
"id": "23",
"name": "Way Lau",
"links": [
{
"rel": "self",
"href": "/api/v1/drivers/23"
}
]
}
]
}

回到我们的医院挂号系统案例中,还是使用在第2级中使用过的GET作为首个请求。

GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk

但是响应中添加了一个新元素。

HTTP/1.1 200 OK
[省略了其他头的信息……]
<openSlotList>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450">
<link rel = "/linkrels/slot/book"
uri = "/slots/1234"/>
</slot>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650">
<link rel = "/linkrels/slot/book"
uri = "/slots/5678"/>
</slot>
</openSlotList>

每个就诊时间信息现在都包含一个URI,用来告诉我们如何创建一个预约。

超媒体控制(Hypermedia Control)的关键在于:它告诉我们下一步能够做什么,以及相应资源的URI。比如,我们事先就可以知道去哪个地址发送预约请求,因为响应中的超媒体控制直接在响应体中告诉了我们该如何做。

预约的POST请求与第2级中类似。

POST /slots/1234 HTTP/1.1
[省略了其他头的信息……]
<appointmentRequest>
<patient id = "jsmith"/>
</appointmentRequest>

响应包含了一系列的超媒体控制,用来告诉我们后面可以进行什么操作。

HTTP/1.1 201 Created
Location: http://royalhope.nhs.uk/slots/1234/appointment
[省略了其他头的信息……]
<appointment>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
<link rel = "/linkrels/appointment/cancel"
uri = "/slots/1234/appointment"/>
<link rel = "/linkrels/appointment/addTest"
uri = "/slots/1234/appointment/tests"/>
<link rel = "self"
uri = "/slots/1234/appointment"/>
<link rel = "/linkrels/appointment/changeTime"
uri = "/doctors/mjones/slots?date=20100104@status=open"/>
<link rel = "/linkrels/appointment/updateContactInfo"
uri = "/patients/jsmith/contactInfo"/>
<link rel = "/linkrels/help"
uri = "/help/appointment"/>
</appointment>

超媒体控制的一个显著优点是:它能够在保证客户端不受影响的条件下,改变服务器返回的URI方案。只要客户端查询“addTest”这个URI,后台开发团队就可以根据需要随意修改与之对应的URI(只有最初的入口URI不能被修改)。

超媒体控制的另一个显著优点是:它能够帮助客户端开发人员进行探索。其中的链接告诉了客户端开发人员下面可能需要执行的操作。它并不会告诉所有的信息,但是至少它提供了一个思考的起点,引导开发人员在协议文档中查看相应的URI。

同样地,它也让服务器端的团队可以通过向响应中添加新的链接来增加功能。比如,如果客户端开发人员发现了一个之前未知的链接,那他们就会知道这个链接是服务器端提供的新的功能。

REST API管理

下面介绍几种简洁的REST API设计的最佳实践,可以作为真假REST的一个判别依据。

1.使用的是名词而不是动词

使用名词来定义接口。

/resources
/resources/1024
不应该使用动词:
/getAllResources
/createNewResource
/deleteAllResources

2.GET方法和查询参数不能改变资源状态

如果要改变资源的状态,要使用PUT、POST和DELETE。下面使用GET方法来修改user的状态是错误的:

GET /users/711?activate
或
GET /users/711/activate

3.使用名词复数

不要混淆名词的单复数。保持简单,只用复数名词来定义所有资源。

/cars 代替 /car
/users 代替 /user
/products 代替 /product/settings 代替 /setting

4.使用子资源来表达资源间的关系

GET /cars/711/drivers/ 返回 711 号 car 的所有 driver 列表

GET /cars/711/drivers/4 返回 711 号 car 的 4 号 driver

5.使用HTTP header来序列化格式

客户端、服务器都需要知道相互之间的通信格式。这些格式可以定义在HTTP header里面。

·Content-Type定义了请求格式。

·Accept定义了接收相应的格式列表。

6.使用HATEOAS约束

HATEOAS是REST架构风格中最复杂的约束,也是构建成熟REST服务的核心。它的重要性在于打破了客户端和服务器之间严格的合约,使得客户端可以更加智能和自适应,而REST服务本身的演化和更新也变得更加容易。

下面是一个HATEOAS的例子。

{
"id": 711,
"manufacturer": "bmw",
"model": "X5",
"seats": 5,
"drivers": [
{
"id": "23",
"name": "Stefan Jauker",
"links": [
{
"rel": "self",
"href": "/api/v1/drivers/23"
}
]
}
]}

7.提供过滤、排序、字段选择、分页

过滤:

GET /cars?color=red
GET /cars?seats<=2

排序:

GET /cars?sort=-manufactorer,+model

字段选择:

GET /cars?fields=manufacturer,model,id,color

分页:

GET /cars?offset=10&limit=5

8.API版本化

版本号使用简单的序号,并避免点号,如2.5等。正确用法如下。

/blog/api/v1

9.充分使用HTTP状态码来处理错误

HTTP状态码(HTTP Status Code)是用来表示网页服务器HTTP响应状态的3位数字代码。它由RFC 2616规范定义,并得到RFC 2518、RFC 2817、RFC 2295、RFC 2774、RFC 4918等规范的扩展。

在设计API处理错误时,应该充分使用HTTP状态码,而不是简单地抛出一个“500-Internal Server Error(内部服务器错误)”。所有的异常都应该有一个错误的payload作为映射,下面是一个例子。

{
"errors": [
{
"userMessage": "Sorry, the requested resource does not exist",
"internalMessage": "No car found in the database",
"code": 34,
"more info": "http://dev.mwaysolutions.com/blog/api/v1/errors/12345"
}
]
}

最后

拓展干货阅读:一线大厂面试题、高并发等主流技术资料

码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!

以上是关于分布式系统核心:REST风格的架构,REST成熟度模型及REST API管理的主要内容,如果未能解决你的问题,请参考以下文章

HATEOAS约束

分布式系统REST风格架构常用技术:Jersey,ApacheCXF,Spring MVC

HATEOAS约束

什么是REST架构

什么是REST架构

SpringBoot HATEOAS用法简介