RESTful 有用吗? HTTP 有 GET POST 就足够了?

2017-02-15 12:59:26 +08:00
 noli

不少程序员都是这么认为的,基于 HTTP API 的服务,只要用 GET 请求和 POST 请求就足够了。 像 RESTful 这样的 大量使用 PUT , DELETE 请求是不必要的。

真的吗,我来举一个例子。

假设有一类资源 ResourceXYZ ,对其有增删查改的操作。 如果只使用 GET POST 之类的设计方式,那么很可能会设计以下的请求接口:

POST .../addResourceXYZ
POST .../delResourceXYZ
GET .../getResourceXYZ?resourceId=resourceId
POST .../updateResourceXYZ

如果按照 RESTful 的 设计方式,很可能会设计以下的请求接口

POST .../ResourceXYZs
DELETE .../ResourceXYZ/{resourceId}
GET .../ResourceXYZ/{resourceId}
PUT .../ResourceXYZ/{resourceId}

现在假设,客户端要获取该资源,其 ID 为 resourceId 。 如果成功,那么一切都好说。 如果失败, Restful 的处理方式是,通过 HTTP status 返回错误码来表示原因,例如 404 表示该资源不存在。

那么只用 GET POST 两种方法的方式呢? 响应请求

GET .../getResourceXYZ?resourceId=resourceId

的时候能不能也用 404 呢?

按照 404 的语义,响应 404 是不对的: 因为客户端请求的 URL 实际上是正确的,只是对应的参数没有找到对应的结果 很多时候,就只能靠响应 200 然后返回空数据或者空对象来处理了。 例如 Content-type 为 application/json 时,可以返回 {} 或者

{
    "error": "not found",
    "code": 404
}

这样就会要求客户端,必须处理 HTTP 回复的具体内容,而不能只处理头部。 那么客户端要怎么处理这个 json 呢 要先解析 json ,然后尝试分别这是一个资源的内容,还是一个错误提示。

对于强类型语言例如 C/C++ OC Swift 写的客户端来说,恐怕就忍不住要问候服务端程序员一家了。

更重要的是……

没有库会支持这种拍脑袋式的设计。

全文完,欢迎拍砖。

42350 次点击
所在节点    程序员
207 条回复
Balthild
2017-02-25 16:39:25 +08:00
@noli 請先證明「自身水平沒有欲批評的對象高,便無資格作批評」這個命題。
noli
2017-02-25 19:49:23 +08:00
@Balthild 说你不够资格并不是否定你评论的权利,只是想让大家以及你确定,是否在浪费时间。然而现在看来你确实是浪费时间。

1 你当然可以设计这样的规范,并且或许已经有这样的实现。但是并没有普及。我已强调过了,有现成的规范你不妨介绍给大家。
但是,“动作”本身就是业务的一部分。如果真的有这么一份规范,并且每个动作都形成具体的规定,我预感它要遵守的规范也好习惯也好不比 HTTP 动作简单。
再者,”动作“本身是不是一定要有原子性语义,这还是业务决定的。所以你的提议听起来感觉就很临时。

2. CRUD 是普遍存在的需要。把 CUD 三类请求放在了 POST 里面不是不可以,但这样你又必须把动作写在业务代码里面,既然这样,为什么不直接用 HTTP 语义?
再往深了说,非纯函数式编程的语言里面,哪个语言没有这个过程:定义符号(变量)-> 读取/修改符号(变量) -> 符号(变量)离开作用域。 POST GET PUT DELETE 恰好就是这个过程的对应。换句话说,遵守 RESTful 规范,你的系统潜在地拥有了一个描述符号演算系统的能力,这比你什么鬼“动作”有根据多了。

3. 脱离业务实现谈效率是扯淡。

“自身水平沒有欲批評的對象高,便無資格作批評”

不解释。
Balthild
2017-02-28 20:31:47 +08:00
@noli

2. 你理解错了,我举这个例子只是为了讽刺你这这种「能用那四个动词描述的就用那四个动词,不能的就视作事务」之做法。
既然依你所言,事务可以视为资源,同时又有一切操作都可以被事务包括之前提,那么 API 便设计成甚至只用 POST 都可以,而且这还能同时遵守 REST ——这才是我想表达的讽刺语义。

3. 我举出例子来说明用 DELETE+POST 来实现 Move 动作的荒谬性,本身就是为了指出你脱离具体业务之错误。
noli
2017-03-01 02:13:37 +08:00
@Balthild
你非要认为移动是特殊的动作。业务许可的话,难道就不能一个 Put 或者 Patch 请求解决么。把资源的位置视作一种扩展属性,这样不 RESTful 么?这根本不是什么本质上的缺陷。

我已经解释过了,有很多种抽象的方式和角度,但是 RESTful 大多可以满足。要做成事务还是两个请求还是一个请求,看具体需要。视乎系统怎么设计有什么需求。

你非要用你死板而缺乏想象力的方式来想象我使用 RESTful 的方式,我只能认为这是不断给我打你脸的机会。
Balthild
2017-03-03 13:58:03 +08:00
@noli

2. 这当然很 RESTful ——我前面已经讲了。我是指出,这种方式和其他只使用 GET/POST 的 API 风格并没有本质上的区别。

3. 终于改口了啊,现在你说「 RESTful 大多可以满足」。可之前你说的却是「除了无限的东西外其他东西都可以抽象成资源」「抽象不成是你水平低」。
我本来就在强调看需求,不顾需求强行 REST 的人是你。

我死板地想象,这我千真万确地承认,所以建议你不如明白地解释解释,你是怎么用包含「删除+重新创建」的事务「移动」一个几 GB 的资源的。
Balthild
2017-03-03 13:59:38 +08:00
@noli 请问如果把资源的位置视为扩展属性,用 PUT 一个 Patch 来解决移动操作,就不需要专门实现一下吗?别跟我说你内部还是用「删除+创建」来处理的。
noli
2017-03-03 16:17:14 +08:00
@Balthild

2. “用 PUT 或者 PATCH ” 当然和 “用 GET / POST ” 有本质的区别。

我肯定你只是一直在 blah blah blah 而没有看到过我前面说过的区别,区别在于:如果 PUT 或者 PATCH 失败了,你可以通过 HTTP STATUS 知道是资源不存在还是 API 调用有错误,但你只用 GET / POST 没法保证只需在 HTTP 层解析就知道是什么错误。

这个问题就好像你调用 int GetNumber() 这样的接口,如果返回 -1 你无法判断这是一个错误还是一个结果。
而现代的做法应该是类似于 Either<int, Error> GetNumber() .

你要说前者足够你的业务使用就算了,这没问题,你喜欢就好。
但非要指责抽象形式 Either<int, Error> 是死板,我只能说这是井底之蛙夏虫语冰。

3. 你倒是找一个 RESTful 做不出来的需求啊…… 抓文字游戏胜利了显得你很聪明?

“ PUT 一个 PATCH ” 这种说法让我发现回答你真是浪费时间,原来你连 RESTful 除了 PUT 之外还有 PATCH 都不清楚啊……

你觉得你还好意思跟我 讨论 RESTful 不行么?
自己去看吧,不用谢: https://tools.ietf.org/html/rfc5789

移动一个资源怎么用 PATCH ?简单:

PATCH .../HugeResource/1234567

{"oldPath": "../old/path/to/resource",
"newPath": "../new/path/to/resource"
}

不改变资源的物理位置,改变资源挂载位置,这是不是移动?嗯?

我都说了,只要你的业务可以设计成这样,就可以支持这样的操作,跟 RESTful 一点关系都没有
难道你只会用 cp 和 rm 不知道有 mount ?
Balthild
2017-03-04 13:41:51 +08:00
@noli
2. 类比错误。
用 HTTP StatusCode 来表示错误才是相当于 int GetNumber() ,且用返回之负数的值来区分错误。
<int, Error> GetNumber() 则是类似于返回 json 来描述错误。

3. 哈哈哈哈哈哈哈哈哈哈哈哈 REST 提出之后十年才有 HTTP PATCH 方法呢……

你看,你为了套 REST ,你又要特意增加一个「挂载位置」的概念。为了区分物理位置和挂载位置,整套系统的复杂度就增加了。况且在意义上,这和专门实现一个 move 动作有区别吗?哦,我是指「是否符合 REST 之外的区别」。
noli
2017-03-04 14:48:49 +08:00
@Balthild

1. 不明白为什么是“类比错误”。

为什么你认为 “用 HTTP StatusCode 来表示错误才是相当于 int GetNumber() ,且用返回之负数的值来区分错误。”
以及“ <int, Error> GetNumber() 则是类似于返回 json 来描述错误”?

没说理由,你太着急反驳我了吧。


2. 你既然有“移动资源位置” 的概念,那肯定本身就有“资源存储位置” 的概念,或者“资源挂载位置” 的概念,两者之少有一个。

所以不存在你说的,“为了套 REST ,你又要特意增加一个「挂载位置」的概念”。
不知道你有什么好装 B 的。


* 针对只有“资源存储位置”概念的系统 -> 做成事务封装两个请,求或者直接发送两个请求,或者再包一层 API 来决定要用哪种方式

总之,物理上改变资源存储位置肯定是两个, API 形式上是一个调用,只不过是选择不让人看到这个事实 ;暴露两个原子的调用接口的 API 设计选择,是选择让调用者知道更多。

这只是一种口味问题,你把这种问题偏离到 “套用 REST ” 只能说明你根本没仔细想过。

* 针对支持“资源挂载位置” 概念的系统 -> 做成修改资源挂载位置的请求 。

支持以上这些, RESTful 都是毫无问题的。

---

为什么我认为 "用 HTTP StatusCode 相当于 返回 Either<int, Error>", 我的理由是:

1. 对所有的使用者来说,结果的理解方式是唯一的,要么 match(int), 要么 match(Error)

2. 对所有获得 HTTP Response 的使用者来说,要么 match 到他关心的 HTTP Status Code ,例如 2XX, 3XX, 4XX
要么 match 到它可能不关心的 match(Error) ,于是处理这个错误时可以不用管 Error 具体是什么(具体错误内容在 Http Response Body 里面,通常是 Json ),这些通常是非业务相关的中间设备,例如缓存服务器, HTTP 网关之类的;
也可以是业务相关的设备,这些设备可以进一步处理 Error 的解析(也就是解析 Response Body 里面的 Json )


为什么我认为 只使用 GET POST ,相当于返回 int ,

就如同我本帖一开始说的那样,相当于要求所有参与调用这个 API 的人知道, int 的含义并不是简单地一个数值,而是在某个数值范围内才是正常的结果,在某个数值范围内是表示错误。

只使用 GET POST ,
如果不是一直使用 200 ,那么就有混淆资源相关错误和 API 错误的问题;
如果一直只使用 200 ,那么就强迫所有使用者必须了解业务细节才能理解 HTTP Response ,对有 HTTP 中间非业务相关的设备来说,肯定是不友好的。

当然,很多系统没有大到拥有非业务相关的 中间 HTTP 设备。

所以对于这种系统,这是一种传统约定的偷懒行为,能 work ,但并不是什么好的设计。
这样的系统一旦扩展,需要添加业务不相关的 网关、缓存之类的设施之后,就会因为 API 设计的偷懒 而 要复出 重构的代价。
Balthild
2017-03-07 11:12:39 +08:00
@noli
1. 相反,如果不一直使用 200 ,才会导致 API 自身错误和资源相关错误。此时如返回一个非 200 的 HTTP 状态码,调用者会无法判断这个错误是否经过了 API 的业务逻辑才返回的。所以我的做法是经过了业务逻辑才返回的错误放在返回的 json 中而不是 HTTP 状态码——这就是为什么我认为<int, Error> GetNumber() 是类似于返回 json 来描述错误。

2. 不把位置视为资源的扩展属性,则资源的逻辑位置和物理位置可以用一套自然的对应法则进行对应。否则,你必须手动实现物理位置和逻辑位置的非自然的对应关系——通常得把它写进数据库,资源被访问时去查询。
noli
2017-03-07 14:21:56 +08:00
@Balthild

1. “调用者会无法判断这个错误是否经过了 API 的业务逻辑才返回的”

很明显这是因为没有正确地使用 HTTP Header 。
调用者只需要分析一下 Content-type 和 Server 头就知道是否经过 API 的业务逻辑。

但说实话,作为 API 调用者的 HTTP Client ,知道是否经过 API 调用这真的重要吗?
这是否意味着调用者与 API 服务之间的过度耦合?
如果确实未经过 API 服务就得到了 HTTP 回复难道调用者就不需要处理了吗?

所以这是一个因为错误的做法而产生的伪问题。
错误的根本原因就在于对 HTTP 的能力了解不足,才会说只要 200 和 GET POST 就够了。

2. 什么叫做“自然的对应法则”?

我不觉得你说的“自然的对应法则” 不适用 “存储位置” 和 “挂载位置” 两种概念中的一种。

你能够 “不把位置视为资源的扩展属性” 那纯粹是业务不需要。

拿 “邮件”作为资源的具体例子,对应“发送”“归档”“删除”“移动”。

你解释一下你的所谓 “自然法则”是什么。
反正我是觉得肯定跑不开 “存储位置” 和 “挂载位置” 这两种。

“存储位置”:邮件数据在物理存储中的位置,无外乎 数据库中,文件系统中
“挂载位置”:邮件属于哪个逻辑存储位置,例如某某用户的某某文件夹下,某某分类或者聚合的某某路径下。

你乐意的话,讲讲你的 GET POST 怎么完成上面的业务概念。

我已经证明过 RESTful 无论应对哪种需求的展开都有完美的办法。
Balthild
2017-03-10 14:58:02 +08:00
@noli

1. 不需要,由于单个动作所具有的原子性,遇到未经过业务逻辑的响应直接丢弃并停止即可。

2. 我说的自然对应法则指的是无例外的、一一对应的法则。
如果只移动逻辑位置而不移动物理位置,必定会造成法则中存在例外情况,而例外情况的存储、查询又要单独处理。
如果直接移动物理位置,就不会在一套对应法则中存在例外。
noli
2017-03-10 15:19:29 +08:00
@Balthild

1. 不说逻辑处理的细节都是在扯淡。如果业务协议都是 GET POST 200 ,要怎么写代码才能知道是未经业务逻辑响应?
难道不需要尝试解析一下报文吗。
一句话轻轻带过,你也就是用用框架写写业务代码的水平。
讨论 REST 太为难你了。

2. 不说人话,我也不想和你讨论了。
请你离开此贴。
Balthild
2017-03-26 14:54:21 +08:00
@noli
1. 难不成用 REST 就不需要解析?不用 rest 我可以判断完 status code 直接走解析流程,数据都是预定义的、结构化的。用 REST 除了区分 status code ,还要搞一堆你说的乱七八糟的东西,完全是多此一举。
我不仅写后端的业务,我还同时要写前端的。在什么场景下的前后端之间怎么进行数据的交互最方便,恐怕只写后端的人是没有这方面的实践的。
和你讨论 REST 相当为难我,因为我确是在和一个凡事必 REST 而不考虑实际情况的人谈。

2. 你可以选择 Block ,我倍感荣幸。
noli
2017-03-26 15:54:20 +08:00
@Balthild

1. 请你先搞清楚,我说的 “报文”不包括 HTTP 头。

GET POST 200 status code 就是得必须解析报文。 正确使用 HTTP STATUS code 例如 REST 的做法,就肯定可以不解析报文。

写过前后端,也就是搞搞 界面搞搞 业务协议的程度吧,这一点都没有改变你对 HTTP 的无知。
写过 怎么从 stream 开始解析 HTTP 请求的代码吗?
如果你真的写过就知道你们这些所谓的 200 党是多么弱智。

你倒是说说什么 HTTP 上的应用 REST 是解决不了的啊,光喊靠这种那种卑鄙暗示对手愚蠢,有用吗?
承认自己经验少不就可以了嘛,非得死撑。
Balthild
2017-04-19 09:21:04 +08:00
@noli
1. 哦,懂了,无非就是把一部分解析工作甩给请求库吧?
2. 说过了,我的确经验少,然而反例遇到过一个就够了。
noli
2017-04-19 10:18:23 +08:00
@Balthild 反例一个就够这种话,也就你们这种学生在经验中只发现一个影响因子才会这么推论的吧。说到底还是放弃了用脑子实事求是地分析,依靠本来就少的经验来做经验主义推导。跟你浪费了这么多的时间,都没让你发现你其实自己其实是个浪费别人时间的人,是我的说话方式不够振聋发聩。
这种事情自己实践一遍就知道了。实在是懒,那就用英文搜一下别人怎么看的。就一个简单的协议封装与信息跨层提示问题,拿例子掰开揉碎来讲都不懂。只能说机缘未到。
Balthild
2017-04-19 10:51:42 +08:00
@noli
请复习高中数学必修三逻辑学入门,全称命题的否定是一个存在命题,即找出至少一个反例就足够否定全称命题。

说到底 REST 本身不过是 HTTP 上抽象出来的某种模型,这种模型的描述能力本身就只能靠经验来支持。而你又找不出某种类似于对编程语言之图灵完备一样的理论性标准来证明其描述能力,因此反例对一种模型在特定场景的描述性的否定是完全没问题的。

不同人设计出的模型是有优劣,我不否认。然而什么时候总体上有优势的模型必定在任意场有优势了?大学里辩证唯物主义都白学了?
noli
2017-04-19 11:20:53 +08:00
@Balthild 建模错误。

我不学辩证唯物主义,我大学不考马列毛邓,谢谢。

而且你的辩证唯物主义恰恰犯了常见唯物主义者的错误,就是滥用或者随意扩张词语的意涵。
什么叫做“总体上有优势”的模型,什么叫“任意场”?
讨论 REST 的时候的时候指代什么?
Balthild
2017-04-19 11:34:34 +08:00
@noli 马哲是大学必修课程,莫非你自愿放弃学分?

打少了个字,是任意场合。

请指明我扩张了哪些词语的意涵?

总体上有优势,指的是一种模型在大多数场合下相比其他模型更加适用。
在更加优秀的模型被提出之前, REST 在 HTTP 通信中的大多数场合是比其他模型更加适用的。但不代表 REST 在任意的场合都适用。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/340607

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX