node.js express mongoose
1
Oktfolio 2020-06-22 11:09:03 +08:00
杠一下,这并不 RESTful 。
|
2
youxiachai 2020-06-22 11:09:21 +08:00
居然不用 ts..写后端...
|
3
libook 2020-06-22 12:18:54 +08:00 2
看了 API 的定义之后,认为完全没有遵循 REST 的方法论来设计。
可以看看维基百科,国内也有很多 REST 的书可以看,英语过关的话可以看看 Roy Fielding 的 paper 。 我举个例子吧,就说你现阶段做的用户部分。 REST 是围绕 Resource 来设计的,那么“用户”相关的功能肯定都是以“用户”为 Resource 的,而 REST 提供了一个基本思路就是使用 HTTP 的 method 来代表逻辑上对资源的操作: POST:创建 GET:获取 PUT:修改 DELETE:删除 那么相应的,用户的 API 有这几个: 创建一个用户:POST /user 获取一个用户:GET /user 修改一个用户:PUT /user 删除一个用户:DELETE /user 使用 querystring 来进行查询,比如删除 id 为 123 的用户: DELETE /user?id=123 有时候会有需求一次对多个用户进行操作,那么这时候有两种思路: 1. 把“一批用户”作为一个独立的 Resource: 创建一批用户:POST /user_batch 获取一批用户:GET /user_batch 修改一批用户:PUT /user_batch 删除一批用户:DELETE /user_batch 2. 让“用户”Resource 具有“批量”的 Action: 创建一批用户:POST /user/batch 获取一批用户:GET /user/batch 修改一批用户:PUT /user/batch 删除一批用户:DELETE /user/batch 用哪种思路可以带入你的实际业务来看未来哪种会更好用一些。 那么对于“登录”、“登出”等特殊逻辑,如果依然在用户 Resource 上考虑,是无法简单使用 HTTP method 来代表的,可以抽象成独立的 Resource: 登录(创建一个会话):POST /session 登出(删除一个会话):DELETE /session 修改用户昵称和修改密码都是 PUT /user,可以通过判断 Request Body 里传入的是 nickname 还是 password 来决定究竟是调用修改昵称的逻辑,还是调用修改密码的逻辑,还是两个都调用。 以上就是 RESTful API 设计的一点皮毛,一些需要注意的点: 1. REST 是一种设计风格,本身并未提供 API 的细节设计,需要自己理解 REST 解决痛点的核心思路,再根据实际业务情况来设计 API 。 2. 如果业务上没有相应的痛点需要 REST,甚至本身与 REST 的思路相矛盾,就不要硬上 REST,例如微服务通信非常适合用 REST,但 BFF 或 API 网关层接口聚合可能更适合用 GraphQL 。 3. Resource 就像是一个对象,其所有接口都应当使用同一套属性定义,比如修改密码的时候使用 password 字段在代表密码属性,那么在获取用户密码哈希的时候就不应该用 password 字段了,应该用 passwordHash 字段,password 字段应该始终代表用户的密码属性,即便这个属性没有存入数据库无法被 GET 出来。 4. API 上定义的 Resource 不一定要和数据库里定义的 Model 一致,比如用户会话“Session”Resource 可能不会存在数据库里,但是在 API 上它可以是个 Resource 。一个更极端的例子是 API 层的用户 Resource 叫“User”,而底层服务数据接口层面可以叫“Customer”,而且双方的数据结构可以有很大区别,通过业务逻辑来进行映射。 5. 一个 Resource 可能会有多个逻辑对应同一个增删改查操作,这时候可能就需要引入 Action 机制,即在每一个 Resource 后面都可以跟一个 Action,来代表执行不同的功能逻辑,如果有多级 Resource 的话,也最好确保每一级 Resource 后面紧跟的都是其 Action 。 6. HTTP 标准并没有说仅 GET method 能用 querystring,甚至没有规定 GET method 不能用 body (虽然很多 HTTP 库禁止 GET 请求传 body,但并不说明标准不允许这么做,实际上 Elasticsearch 的 API 就是在使用 GET method 请求的同时传 body 的),实际上 HTTP 标准中 method 、querystring 、header 、body 是分别定义的,可以自由组合使用。 7. 要合理规划 method 、querystring 、header 、body,哪类参数应该用什么机制来传,要做到始终遵循一套标准。 |
5
iplayio2019 2020-06-22 12:26:23 +08:00 via Android
@sirnay 复数
|
6
ugu 2020-06-22 12:36:42 +08:00
这并不是 restful api
|
7
hantsy 2020-06-22 12:44:29 +08:00
@libook URI 全称 统一资源定位器(好像中文是这样的),那 URI 本身应该可以区分到一个资源的集合,或者单独的资源。ID 从来都是放在路径中来表示单个资源。
GET,DELETE 不接受 Body 内容,POST,PUT 接受 Body 内容。 Eg:(没办法排版就只能这个样子了,后面()是 Reponse 返回约定) GET /posts?q=&limit=&offset= ( 200 [{...}]) GET /posts/:id (200 {...}) POST /posts (201, Header Location 指向新建资源 URI) PUT /posts/:id (204) DELETE /posts/:id (204) |
8
hantsy 2020-06-22 12:52:21 +08:00
我现在在写一个 Nestjs API 的 Sample,目前还在摸索 Nestjs 特性中。
https://github.com/hantsy/nestjs-sample 这个 CRUD Sample 中,目前仅 URI,HTTPMethod 基 本上与我上面列表一致,Response 后面会修正(等我搞完现在的 Mongoose 模型关系与 Auth,会修改)。 我这个列表仅仅是达到 Richardson Mature Model Level 2 标准的参考,还没有达到 Level 3 。当然 Level 3 表现形式太多了,有点眼花瞭乱,一般项目应用不是很广泛,但是些公开的 API 很多也实现了,比如 Github 上一代(现在 Github 最新 API 的切换到 GraphQL )。 |
9
hantsy 2020-06-22 12:56:51 +08:00
|
10
hantsy 2020-06-22 12:58:57 +08:00
楼主 API 应该未达到 Level 1 。
|
11
iplayio2019 2020-06-22 13:09:16 +08:00 via Android
@libook 资源名词用复数,delete put 用的也是路径参数。还有一个 patch 方法用来更新某些字段的。
|
12
hantsy 2020-06-22 13:46:08 +08:00
@iplayio2019 Patch 可以结合 JSON Patch 、Merge 等标准,这个在 Java EE 标准 Jaxrs,JSON-P 等标准中已经实现了。
实际操作中以前一个项目实现过,用起来麻烦(各种客户端不一定支持)。 http://jsonpatch.com/ 为了使用 Patch,可以简单自己定义一些操作,类似 PUT 操作。如: PATCH /posts/:id/status (204) 更新状态,如 DRAFT-> PUBLISHED |
13
libook 2020-06-22 14:06:58 +08:00
@hantsy
URI 不是 REST 的概念,是 RFC-3986,而在这个标准里 URI 的定义很宽泛,并没有强制要求“ID 从来都是放在路径中来表示单个资源”。 我没讲 ID 放在 Path 里,是考虑 Action 会和 ID 占用同一个位置,需要增加额外逻辑判断 Resource 后面跟的是 ID 还是 Action 。如果业务逻辑上比较简单,且常用 ID 来定位 Resource,采用 Path 里传 ID 的方式也是可以的。 不过并不是说“可以在 Path 里用 ID 来定位 Resource”,就“不能在 querystring 里用 id 作为查询条件”,完全可以两种都用,根据实际业务来制定哪些情况下用 Path 传 ID 的方式,哪些情况下用 querystring 的方式。 我以前遇到过在批量操作的时候希望定位多个 ID 的需求,这时候就只能通过一些数组传参的方式来实现了(比如 GET /user_batch?id=abc&id=cba,具体看团队内的标准)。根据自己的实际需求来设计就好。 @iplayio2019 代表批量资源的时候不建议使用英文复数,因为英语的构词法是不严格的,遇到单复同形的情况下(比如 fish)就没法区分单个还是多个了。 REST 是制定项目 API 标准的参考思路,而不是 API 标准,其本身也没有提供 API 设计细节,具体的解决方案还是得根据实际需求具体设计。 我只是分享我自己踩过的一些坑,指出哪些设计适用哪些情况、不适用哪些情况,希望能帮助楼主少走弯路,各位回复的时候也请说明一种方案在哪些情况下好用,在哪些情况下不好用,一个需求在不同情况下能提供多种方案就更好了。 |
14
hantsy 2020-06-22 17:02:31 +08:00
@libook REST 是约定没错,既然如此随意,回到 HTTP 就好了, 国内公司基本都是认同自己随便设计,何必要说它是 REST 了。
但我个人认同 Richardson Maturity Model 的描述,这个可是评估 REST 质量方面很具体的东西。 1,当你的 URI 能够 Identify 资源,比如 /posts,/posts/what-is-rest 就达到了 Level 1 。这一点,ID 作为 URI 一部分去 Identify 资源是必须的。 2,Level2: 当你的设计在 Level 1 基础上体现了 Http Verb,Http Status 表示响应状态。 3,Level3:Self-document 目前,HAL,HAL Forms,JSON Collections 等标准在为这个努力,做到自我说明,不需要文档困难 |
15
iplayio2019 2020-06-22 22:18:16 +08:00
@hantsy 老铁,URI 是统一资源标识符,URL 才是统一资源定位符的意思。URI 是互联网资源的一种抽象,URL 是 URI 的具体实现。
@libook RESTful 里有两个主要的类型:集合和实例。集合必然是复数,不会使用单数。而实例可以使用单数。 例如 /profile 这种就可以使用单数,再比如当前用户 /user,也就是当你处于登录状态时查询你的信息时,path 可以设计为 /user 。还有 /search 也是一个单个实例。这是实例,可以用单数。 我个人还是推荐使用复数更好一点,使用复数是 RESTful 的一种约定,我看到的 RESTful 实现都是都是复数名词。大部分的英文单词都有复数,不要因为一小部分的英文名词没有复数而使用单数。 很多大厂已经有 RESTful 的实现了,可以看看 github 的或者 WordPress 的 RESTful 的实现。 另外,个人不推荐使用动词在路径参数中,因为这和 RESTful 的“资源”概念是相违背的,应该以资源的角度去规划 URL,使用 HTTP 方法去描述动作。如果你要使用更多的操作,可以使用 PATCH 去操作,在 body 里面增加 OP 属性。 对于 POST 操作一定是新增一条数据,而 PUT 是整体的更新,PATCH 是部分更新。 我也很认同 @hantsy 的观点,既然如此随意,何必还要去关心的 RESTful 的约定。 |
16
hantsy 2020-06-23 10:52:17 +08:00
@iplayio2019 REST 设计,Github,Heroku 的 API 可以说是参考的典范。
Patch 这个我个人是很少用(除了我上面提到自定义更新一些属性的例子),不主张大面积用。 以前一个项目客户那边做 Engineering 的要求全部资源提供 Patch,用了 JSON Patch 规范结合,实现用的 Spring 下一个项目 spring-sync (已经不更新了)。 像 @libook 提到的批量处理的,如,对于 /posts, 都是可以用一个 JSON Patch 实体包含的内容(它相当于一个 Diff 文件描述,可以单个资源操作,也可以集合操作,进行增,删,改)。这个 JSON Patch 描述设计的非常好,但是不好用,前端生成一个 JSON Patch 实体,和后端处理起来( Java 现在有 JSON-P 规范可以 Merge 操作)都是比较麻烦。 [V 站牛逼,加链接都不能发言] |