关于RESTful设计风格的思考

软件开发从单机软件到互联网化,从互联网单体应用到系统拆分、前后端分离,再到现在的微服务。软件架构越来越庞大复杂,相关联的领域划分更细致、职责更专一,这样各部分之间的交互就很频繁。RESTful作为一种架构风格、一种接口设计规范,简单、标准、易扩展让其得到了越来越多的应用。

什么是RESTful?

REST 全称 Representational State Transfer,翻译为资源表现层状态转化,资源指服务中的实体或信息;表现层指信息的展现形式,如json、xml、html等;状态转化指接口访问过程中数据和状态的变化,作为http无状态服务,业务的实现必然要涉及到数据和状态的变化。

RESTful的本质是一种软件架构风格,核心在于面向资源设计接口,即将所有的接口描述为对资源的操作,目的在于降低开发的复杂性,提高系统的可伸缩性。设计的准则主要有以下三点:

  1. 网络上所有的事物都可以被抽象为资源
  2. 每个资源都有唯一的资源标识,对资源的操作不会改变这些标识
  3. 所有的操作都是无状态的

采用URI标识资源

RESTful Web API采用面向资源的架构,所以在设计之初首先需要考虑的是有哪些资源可供操作。资源是一个很宽泛的概念,任何寄宿于Web可供操作的事物均可视为资源。资源可以体现为经过持久化处理保存到磁盘上的某个文件或者数据库中某个表的某条记录,也可以是Web应用接受到请求后采用某种算法计算得出的结果。资源可以体现为一个具体的物理对象,它也可以是一个抽象的流程。

一个资源必须具有一个或者多个标识,既然我们设计的Web API,那么很自然地应该采用URI来作为资源的标识。作为资源标识的URI最好具有可读性,因为具有可读性的URI更容易被使用,使用者一看就知道被标识的是何种资源,比如如下一些URI就具有很好的可读性。

http://www.artech.com/employees/c001(编号C001的员工)
http://www.artech.com/sales/2013/12/31(2013年12月31日的销售额)
http://www.artech.com/orders/2013/q4(2013年第4季度签订的订单)

除了必要的标志性和可选的可读性之外,标识资源的URI应该具有可寻址性。也就是说,URI不仅仅指明了被标识资源所在的位置,而且通过这个URI可以直接获取目标资源。URI除了可以标识某个独立的资源外(比如http://www.artech.com/employees/c001),还可以标识一组资源的集合或者资源的容器(比如http://www.artech.com/orders/2013/q4)。当然,一组同类资源的集合或者存放一组同类资源的容器本身也可以视为另一种类型的复合型(Composite)资源,所以URI总是标识某个资源这种说法是没有问题的。

使用HTTP方便表示对资源的动作

由于RESTful Web API采用了同一的接口,所以其成员体现为针对同一资源的操作。对于Web来说,针对资源的操作通过HTTP方法来体现。我们应该将两者统一起来,是Web API分别针对CRUD的操作只能接受具有对应HTTP方法的请求。

接下来介绍7个常用的HTTP方法。首先GET、HEAD和OPTIONS这三个HTTP方法旨在发送请求以或者所需的信息。对于GET,相应所有人对它已经非常熟悉了,它用于获取所需的资源,服务器一般讲对应的资源置于响应的主体部分返回给客户端。

HEAD和OPTIONS相对少见。从资源操作的语义来讲,一个针对某个目标资源发送的HEAD请求一般不是为了获取目标资源本身的内容,而是得到描述目标资源的元数据信息。服务器一般将对应资源的元数据置于响应的报头集合返回给客户端,这样的响应一般不具有主体部分。OPTIONS请求旨在发送一种探测请求以确定针对某个目标地址的请求必须具有怎样的约束(比如应该采用怎样的HTTP方法以及自定义的请求报头),然后根据其约束发送真正的请求。比如针对跨域资源的预检(Preflight)请求采用的HTTP方法就是OPTIONS。

至于其它4中HTTP方法(POST、PUT、PATCH和DELETE),它们旨在针对目标资源作添加、修改和删除操作。对于DELETE,它的语义很明确,就是删除一个已经存在的资源。我们着重推荐其它三个旨在完成资源的添加和修改的HTTP方法作一个简单的介绍。

通过发送POST和PUT请求均可以添加一个新的资源,但是两者的不同之处在于:对于前者,请求着一般不能确定标识添加资源最终采用的URI,即服务端最终为成功添加的资源指定URI;对于后者,最终标识添加资源的URI是可以由请求者控制的。也正是因为这个原因,如果发送PUT请求,我们一般直接将标识添加资源的URI作为请求的URI;对于POST请求来说,其URI一般是标识添加资源存放容器的URI。

比如我们分别发送PUT和POST请求以添加一个员工,标识员工的URI由其员工ID来决定。如果员工ID由客户端来指定,我们可以发送PUT请求;如果员工ID由服务端生成,我们一般发送POST请求。具体的请求与下面提供的代码片断类似,可以看出它们的URI也是不一样的。

PUT http://www.artech.com/employees/300357 HTTP/1.1

<employee>
  <id>300357</id> 
  <name>张三</name>
  <gender>男<gender>
  <birthdate>1981-08-24</birthdate>
  <department>3041</department>
</employee>
POST http://www.artech.com/employees HTTP/1.1

<employee>
  <name>张三</name>
  <gender>男<gender>
  <birthdate>1981-08-24</birthdate>
  <department>3041</department>
</employee>

除了进行资源的添加,PUT请求还能用于资源的修改。由于请求包含提交资源的标识(可以放在URI中,也可以置于保存在主体部分的资源内容中),所以服务端能够定位到对应的资源予以修改。对于POST和PUT,也存在一种一刀切的说法:POST用于添加,PUT用于修改。我个人比较认可的是:如果PUT提供的资源不存在,则做添加操作,否则做修改。

对于发送PUT请求以修改某个存在的资源,服务器一般会将提供资源将原有资源整体覆盖掉。如果需要进行局部修改,我们推荐请求采用PATCH方法,因为从语义上讲Patch就是打补丁的意思。

无状态性

RESTful只维护资源的状态,而不需要维护客户端的状态。对于它来说,每次请求都是全新的,它只需要针对本次请求作相应的操作,不需要将本次请求的相关信息记录下来以便用于后续来自相同客户端请求的处理。

对于上面我们介绍的RESTful的这些个特性,它们都是要求我们为了满足这些特征做点什么,唯有这个无状态却是要求我们不要做什么,因为HTTP本身就是无状态的。举个例子,一个网页通过调用Web API分页获取符合查询条件的记录。一般情况下,页面导航均具有上一页和下一页链接用于呈现当前页的前一页和后一页的记录。那么现在有两种实现方式返回上下页的记录。

  • Web API不仅仅会定义根据具体页码的数据查询定义相关的操作,还会针对上一页和下一页这样的请求定义单独的操作。它自身会根据客户端的Session ID对每次数据返回的页面在本地进行保存,以便能够知道上一页和下一页具体是哪一页。
  • Web API只会定义根据具体页码的数据查询定义相关的操作,当前返回数据的页码由客户端来维护。

第一种貌似很智能,其实就是一种画蛇添足的作法,因为它破坏了Web API的无状态性。设计无状态的Web API不仅仅使Web API自身显得简单而精炼,还因减除了针对客户端的亲和度使我们可以有效地实施负载均衡,因为只有这样集群中的每一台服务器对于每个客户端才是等效的。

RESTful遇到的困境

RESTful的设计风格可以极大提高接口API的易用性,目前越来越多的web服务采用RESTful风格的API对外提供服务,例如:ElasticSearch、MongoDB、Docker API等等。作为一名后台开发工程师,我在工作中也是以RESTful风格指导自己设计对外提供的接口API,在此过程中也踩过一些坑。

一个特别棘手的问题是RESTful风格的API在框架层面不好做监控。通常情况下监控为了不侵入业务代码,框架层面记录每个HTTP请求的URI、方法、返回状态码等信息,然后上报至一个统一的监控中心处理分析数据。以查询雇员信息的API为例,GET /employees/c001GET /employees/c002分别代表查询c001和c002的信息,业务层面上使用了同一份查询逻辑,但是监控数据却显示在两个不同的URI下。

目前我们的解决方案是在业务层面为每个API配置一条监控的URI,这样GET /employees/c001GET /employees/c002这两种请求的监控都会现在查询雇员信息API下,这样可以更容易反应服务执行状态。但是导致的结果就是工作量的增加,本来在框架层面可以统一处理的逻辑却要侵入至各个业务逻辑。

参考资料

[1] 浅谈RESTful接口设计和开发 https://www.jianshu.com/p/d674b6153326
[2] 我所理解的RESTful Web API [设计篇] https://www.cnblogs.com/artech/p/restful-web-api-02.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容