ChatGPT解决这个技术问题 Extra ChatGPT

我应该对 REST 资源使用单数还是复数名称约定?

我是 REST 新手,我观察到在一些 RESTful 服务中,它们使用不同的资源 URI 进行更新/获取/删除和创建。如

创建 - 在某些地方使用 /resources 和 POST 方法(观察复数)使用 /resource(单数)

更新 - 使用 /resource/123 和 PUT 方法

获取 - 将 /resource/123 与 GET 方法一起使用

我对这个 URI 命名约定有点困惑。我们应该使用复数还是单数来创建资源?决定的标准应该是什么?

按照这个主题,我在一篇文章中收集了一些著名的 REST API 示例:inmensosofa.blogspot.com/2011/10/…
我在阅读以下所有答案后得出的结论:始终使用单数,因为(a)它是一致的,(b)它直接映射到单数类和表名,(c)一些复数名词在英语中是不规则的(不可预测的)
请参阅 this answer 以获取单个表命名约定的链接,还有一篇文章提到了这个确切的问题 Rest API Developer's Dilemma - 谢谢@Sorter
我建议使用理查森成熟度模型。这有助于解决这个问题restfulapi.net/richardson-maturity-model

A
Anton

对我来说,最好有一个可以直接映射到代码的模式(易于自动化),主要是因为代码是两端的内容。

GET  /orders          <---> orders 
POST /orders          <---> orders.push(data)
GET  /orders/1        <---> orders[1]
PUT  /orders/1        <---> orders[1] = data
GET  /orders/1/lines  <---> orders[1].lines
POST /orders/1/lines  <---> orders[1].lines.push(data) 

这样做的困难或容易是由于不尊重 HATEOS。它是复数还是单数或其他任何东西都无关紧要。您应该尊重从服务器发送的 uri,而不是在客户端上“建立”您的 uri。然后你有 0 映射为你的代码做。
@richard 客户端仍然需要进行映射。在 HATEOS 中,它们必须映射到表示与 URI 构造的关系 (rel) 的名称。 rel、方法(动词)和 Content-Type 然后组成资源媒体。这并不排除对良好 URI 设计的需要。即使客户端可能会优先考虑 rel 名称,API 的开发人员仍然需要一个良好的人类可读标准来构建 URI。
在我看来,这是一个更好的答案。除了我一直更喜欢使用单数而不是复数。 User.getList()、User.getById、User.delete 等。
我喜欢简单。映射还具有使路由上的文档和测试非常容易编写的好处。
这对我来说很有意义。但是,我们是数据库优先的商店,这意味着我们从数据库模式生成代码和 api 实体。并且数据库标准倾向于提倡单一的表名,所以我们会这样做,但仍然与这个答案的逻辑相同。
M
Mathieu IKO

使用 /resources 的前提是它代表“所有”资源。如果您执行 GET /resources,您可能会返回整个集合。通过 POST 到 /resources,您将添加到集合中。

但是,各个资源可在 /resource 中获得。如果您执行 GET /resource,您可能会出错,因为此请求没有任何意义,而 /resource/123 则完全合理。

使用 /resource 而不是 /resources 与使用文件系统和文件集合的方法类似,/resource 是单个 123 的“目录”,{ 5} 个文件。

这两种方式都没有对错,选择你最喜欢的。


很好的答案!但是 Windows 中的“默认”目录具有复数名称。像“程序文件”、“用户”、“文档”、“视频”等。我也经常在网站网址中遇到复数名称。
几乎大多数人和 API 所采用的事实上的约定是始终保持复数形式。 ID 指定一个资源汽车/ID
“没有对错,走你最喜欢的。”啊,我经常听到的著名台词,厌倦了听到人们的声音。约定很重要,应该在社区中进行建设性的辩论,这就是更好的解决方案和良好实践的来源。当您在 URI 中同时使用复数和单数作为资源名称时,它会使您的代码和 API 复杂化,因为用户和 API 背后的代码必须在路由和逻辑中考虑这一点,以区分单数和复数,而如果您只是坚持一直使用复数你没有问题。
@TomaszPluskiewicz 您完全正确,客户不在乎。作为软件开发人员,我们应该关心——为此我同意 WTF 的评论,即关于约定的建设性辩论是有价值的。
因此,有人可以只给出一个单词的答案并让其接受,这样我就不必(再次)阅读所有内容。
J
Jan Deinhard

我也看不出这样做的意义,而且我认为这不是最好的 URI 设计。作为 RESTful 服务的用户,无论我访问列表还是列表中的特定资源,我都希望列表资源具有相同的名称。无论您要使用列表资源还是特定资源,都应该使用相同的标识符。


就我而言,这是最好的答案。我很欣赏 API 设计人员喜欢说“获取资源 #123”的语言正确性,但是在编写 API 客户端和帮助文档时,这是额外的编码麻烦。 (GET /api/people vs. GET /api/person/123?euuuchh。) .... 不要把它想象成“获取资源 #123”,而是在你的脑海中将其表述为“从资源集合中获取匹配 #123"。
区分复数/单数资源不是关于语言正确性,而是关于规模。 /employees/12 读取给我作为 id 为“12”的员工资源的子集(它可能意味着任何东西,例如最近被解雇的员工的已保存搜索查询)。如果您以 id 为“12”的员工的身份阅读上述内容,您将如何表示子集?唯一的选择是通过使URI 更复杂的矿石来区分包含对象的集合和对象本身(即单数与复数)。
我认为选择 /employees/12 来表示对最近被解雇的员工(或任何子集)的搜索查询将是糟糕的设计。如果您想表示任何类型的子集,我建议将它们作为资源(具有专有名称)单独引入。
这与客户的可理解性无关。这是关于使用不同的 URL 处理不同的事情。并且能够响应所有 HTTP 方法而不会发生冲突。您可以拥有一个作为项目集合的资源,以及一个代表项目本身的资源。就我而言,集合资源可能是example.org/166316e2-e1and该集合中的一个特定项目example.org/20d68348-ccc-001c4200de。客户端不应该构造 URL(显然不能扩展,它不是 RESTful,这就是链接关系类型的用途)。
如果您认为任意 URL 不美观,请随意识别具有复数名称的集合资源和具有单数名称的单个项目。如果您不喜欢英文 URL,并且您的自然语言不支持这种单/复数表示法,请使用其他语言以您的首选语言对其进行定义,我想所有语言都可以让您以某种方式区分 '/the-collection-of- bla/2321' 与 'bla/61' 的书面形式。在发送 GET/PUT/DELETE/POST/PATCH 等时,这两种不同的资源中的每一种都代表完全不同的结果。
E
Eric Knudtson

复数

简单 - 所有网址都以相同的前缀开头

逻辑 - orders/ 获取订单的索引列表。

标准 - 最广泛采用的标准,其次是绝大多数公共和私有 API。

例如:

GET /resources - 返回资源项列表

POST /resources - 创建一个或多个资源项

PUT /resources - 更新一个或多个资源项

PATCH /resources - 部分更新一个或多个资源项

DELETE /resources - 删除所有资源项

对于单一资源项目:

GET /resources/:id - 根据 :id 参数返回特定资源项

POST /resources/:id - 创建一个具有指定 ID 的资源项(需要验证)

PUT /resources/:id - 更新特定资源项

PATCH /resources/:id - 部分更新特定资源项

DELETE /resources/:id - 删除特定资源项

对于单数的拥护者,可以这样想:您会向某人要一个 order 并期待一件事,还是一串事物?那么,当您键入 /order 时,您为什么希望服务返回一个事物列表呢?


单数:如果系统的一部分只有一个对象(0-1,存在与否),例如 users/1/avatar,您可以使用单数形式来标记这个单个对象(例如头像) - 更详细的示例:stackoverflow.com/a/38296217/860099。顺便说一句 - 非常好的答案:)
映射到应该是单数的类名和表名呢? (见other answer
@WillSheppard - 类名最好采用单数形式,表名最好采用复数形式。例如,Order 是处理引用一个订单的对象的单个实例的类的好名称。 OrderList 是处理多个 Order 实例的类的名称。 Orders Table 是一个包含许多订单的数据库表的好名字。
我想获取 /orders 但我只想要 /1
表名的复数与单数争论一直很复杂,因为表定义了它的模式/结构(暗示单数)以及数据的容器/集合(暗示复数)。您在编程语言中没有这个问题,因为类定义是单数的,但持有集合的属性将是复数。重点是,甚至不要尝试将类命名约定应用于表,反过来也不要将表命名约定应用于其余服务。
S
Sorter

单数

方便事物可以有不规则的复数名称。有时他们没有。但是单数名称总是存在的。

例如 CustomerAddress 超过 CustomerAddresses

考虑这个相关的资源。

/order/12/orderdetail/12/orders/12/orderdetails/4 更具可读性和逻辑性。

数据库表

资源代表一个实体,如数据库表。它应该有一个合乎逻辑的单数名称。这是表名称的 answer

类映射

类总是单数的。 ORM 工具生成与类名同名的表。随着越来越多的工具被使用,单数名称正在成为一种标准。

详细了解A REST API Developer's Dilemma

对于没有单数名称的事物

trouserssunglasses 的情况下,它们似乎没有单数对应物。它们是众所周知的,并且通过使用它们似乎是单数的。就像一双鞋。考虑将类文件命名为 ShoeShoes。在这里,这些名称的使用必须被视为一个单一的实体。您不会看到有人购买单鞋以将 URL 设置为

/shoe/23

我们必须将 Shoes 视为一个单一的实体。

参考:Top 6 REST Naming Best Practices


单数名称总是存在的 /clothe/12/trouser/34 :)
@GertArnold clothe 这个词是一个动词。 Rest API 在谈论资源时通常使用名词,而在描述动作时使用动词。单数形式是 clout,但它是古老的,可能更适合用 garment 代替。
@SteveBuzonas 对于裤子和太阳镜?
而相反的 /fish/fish{id}。由于使用了也可能是古老的集体名词,分组时也存在问题:/murders/murder{id}/crow{id}; /gaggles/gaggle{id}/goose{id}。所以复数也可以复数。 “简单的标准规则”永远不会奏效,规则与某处语言的“自然”人类表达性之间总会存在不匹配。真正的问题是 a) 是否接受笨拙的 uri 设计作为事实上的标准 b) 拒绝粗暴和过于简单的“标准约定”。
@Koray Tugay 裤子很有趣,因为它们在历史上被认为是一对(每条腿一条),在整个历史中并不总是需要在顶部连接。所以它们更像是总是成对的袜子或鞋子。
C
Carrie Kendall

尽管最普遍的做法是使用复数形式的 RESTful api,例如 /api/resources/123 ,但在一种特殊情况下,我发现使用单数名称比复数名称更合适/更具表现力。这是一对一关系的情况。特别是如果目标项是一个值对象(在领域驱动设计范式中)。

让我们假设每个资源都有一个一对一的 accessLog,它可以被建模为一个值对象,即不是一个实体,因此没有 ID。它可以表示为/api/resources/123/accessLog。通常的动词(POST、PUT、DELETE、GET)会恰当地表达意图以及关系确实是一对一的事实。


不错的尝试。但作为“accessLogEntries”会更好。 :-)
@TomRussell 为什么?这一点的意义很重要。我理解为什么即使您通过标识符访问资源时也要使用复数,但对于多对一或一对一来说,这是相当误导的。考虑一个管理多地点公司员工的 api。每个工作人员在一个地点工作。 GET /users/123/location 应获取用户工作的位置。 GET /users/123/locations 真的不会误导消费者吗?
@CarrieKendall 我明白你的意思。由于 accessLog 被建模为属性或值,而不是实体,因此它应该是单数的。如果您进行过度设计,那么日志条目将是一个实体,您将拥有 /api/accessLogEntries?resource=123
同意,不过,我认为它确实打破了所有事物多元化的惯例。这是一个棘手的问题。对我来说,简单明了的 API 比 API 更重要,即文档应该补充已经简单明了的实现。
我更像是一名程序员,而不是系统或数据库人员,所以我喜欢一个讲述故事而不是遵守惯例的 API。不过,对自动化文档的影响是真实的。
C
Community

为什么不跟随普遍接受单数形式的数据库表名的流行趋势呢?去过那里,做到了——让我们重用。

Table Naming Dilemma: Singular vs. Plural Names


Das Auto 比 Die Autos 好得多。此外,英语复数约定不一致。
资源命名空间是语义问题,而不是实现问题。所以,用DB表类比,不是很幸运。此外,在使用 DB-s 时,您只操作表,虽然您当然可以影响内容(行),但在 REST 中,直接操作单个资源没有约束。
我认为这是一个很好的类比,但比决定是单数还是复数更重要的是与您选择的任何一个保持一致。您不会插入到用户中,然后从用户中选择。相同的规则应该适用于 REST 资源 - 不要根据您的操作重命名它们。
它不仅仅是表名,它也可以与 OO 中的类名相媲美(我的类将被称为客户而不是客户)。
在这种情况下,语义太重要了,不能简单地接受“已经定义”的趋势
L
Lizzy

我很惊讶看到这么多人会加入复数名词的潮流。在实现单数到复数的转换时,您是否注意不规则的复数名词?你享受痛苦吗?

请参阅http://web2.uvcs.uvic.ca/elc/studyzone/330/grammar/irrplu.htm

有许多类型的不规则复数,但这些是最常见的:

名词类型形成复数示例

Ends with -fe   Change f to v then Add -s   
    knife   knives 
    life   lives 
    wife   wives
Ends with -f    Change f to v then Add -es  
    half   halves 
    wolf   wolves
    loaf   loaves
Ends with -o    Add -es 
    potato   potatoes
    tomato   tomatoes
    volcano   volcanoes
Ends with -us   Change -us to -i    
    cactus   cacti
    nucleus   nuclei
    focus   foci
Ends with -is   Change -is to -es   
    analysis   analyses
    crisis   crises
    thesis   theses
Ends with -on   Change -on to -a    
    phenomenon   phenomena
    criterion   criteria
ALL KINDS   Change the vowel or Change the word or Add a different ending   
     man   men
     foot   feet
     child   children
     person   people
     tooth   teeth
     mouse   mice
 Unchanging Singular and plural are the same    
     sheep deer fish (sometimes)

我不明白这里的担忧。我们不应该以编程方式将单数更改为复数。大多数上述复数形式是众所周知的,不应该是一个问题。如果某人的英语知识很差,他会错误地拼写变量的任何部分。另外,按照您的逻辑,您是否还建议使用单数形式来引用源代码中的集合?
有些英语单词是不规则的,甚至在盎格鲁圈内也经常出现问题,它们是常用的术语,例如索引/索引/索引、顶点/顶点/顶点、矩阵/矩阵/矩阵、半径/半径/半径等。无论如何,我不认为将 REST 路径设为复数有什么意义,因为如果它们都始终是单数,那么对每个人来说都更加明显。
@kishorborate,使用复数作为 URI 更容易出错,即使对于以英语为母语的人也是如此。正如 damd 所指出的,像 index/indexes/indices 这样的复数形式正在引入更多问题。还有不可数名词。将不可数名词与复数混合是另一个问题。为什么让程序员更难在这些方面花费更多时间?我建议对所有事情都使用单数。如果有 /{id},那么 API 应该返回一条记录。如果后面没有 /{id},则 API 应返回该集合。
@DamingFu 奇异资源可能并不总是具有与之关联的 id。例如。 /user/{id}/nickName 看了一下,不清楚是返回nickNames列表还是单个nickName?因此,API 在使用复数形式时更加直观。是的,很少有单词会有不规则的复数形式。对于正在阅读复数形式的人来说,这不是问题。仅在编写 API 签名时才有问题。但是这样的词出现的频率并不高,而且,找到任何一个词的复数形式并不费时。这是我们应该接受的权衡,以使 API 更直观。
c
cosbor11

从 API 使用者的角度来看,端点应该是可预测的,因此

理想情况下...

GET /resources 应该返回一个资源列表。 GET /resource 应该返回一个 400 级的状态码。 GET /resources/id/{resourceId} 应该返回一个包含一个资源的集合。 GET /resource/id/{resourceId} 应该返回一个资源对象。 POST /resources 应该批量创建资源。 POST /resource 应该创建一个资源。 PUT /resource 应该更新资源对象。 PATCH /resource 应该通过仅发布更改的属性来更新资源。 PATCH /resources 应该批量更新仅发布更改的属性的资源。 DELETE /resources 应该删除所有资源;开个玩笑:400 状态码 DELETE /resource/id/{resourceId}

这种方法最灵活、功能最丰富,但开发起来也最耗时。因此,如果您赶时间(软件开发总是如此),只需将端点命名为 resource 或复数形式 resources。我更喜欢单数形式,因为它使您可以选择以编程方式自省和评估,因为并非所有复数形式都以“s”结尾。

说了这么多,无论出于何种原因,开发人员选择的最常用的做法是使用复数形式。这最终是我选择的路线,如果您查看像 githubtwitter 这样的流行 api,它们就是这样做的。

一些决定标准可能是:

我的时间限制是什么?我将允许我的消费者执行哪些操作?请求和结果负载是什么样的?我是否希望能够在我的代码中使用反射和解析 URI?

所以这取决于你。无论你做什么都是一致的。


似乎选择了 plural 形式,因为开发人员似乎假设所有资源本质上都是某个集合的一部分。但是,“接受的约定”似乎表明 POST /users 应该创建一个用户,并将其添加到集合中。我不同意。 POST /users 应该创建一个用户列表(即使是 1 个列表),而 POST /user 应该只创建一个用户。我看不出为什么复数和单数资源端点不能共存。它们描述了不同的行为,不应让任何人对它们的功能感到惊讶。
没有在路径中指定资源 id 的约定吗?如果是这样,它似乎被广泛忽视。例如,POST users/<id> 将创建一个新用户。
@TomRussell 通常是服务器创建 id,所以你还不知道要 POST 的 id。
@TomRussell,当客户端在创建新资源时确定(一种)id 时,使用 PUT /users/<id> 而不是 POST 更为常见。 POST 的解释是“将其添加到集合中,并将 id 确定为其中的一部分”。 PUT 具有“使用此 ID 更新(或添加)此资源”的解释。有关此原理的详细说明,请参见 restcookbook.com/HTTP%20Methods/put-vs-post
@DaBlick - 你能找到你的“最佳实践”来源吗?
S
Shannon Matthews

有关命名资源的另一种看法,请参阅 Google 的 API Design Guide: Resource Names

该指南要求收藏品以复数形式命名。

|--------------------------+---------------+-------------------+---------------+--------------|
| API Service Name         | Collection ID | Resource ID       | Collection ID | Resource ID  |
|--------------------------+---------------+-------------------+---------------+--------------|
| //mail.googleapis.com    | /users        | /name@example.com | /settings     | /customFrom  |
| //storage.googleapis.com | /buckets      | /bucket-id        | /objects      | /object-id   |
|--------------------------+---------------+-------------------+---------------+--------------|

如果您正在考虑这个主题,那么值得一读。


T
TiggerToo

路由中的 id 应该被视为与列表的索引相同,并且命名应该相应地进行。

numbers = [1, 2, 3]

numbers            GET /numbers
numbers[1]         GET /numbers/1
numbers.push(4)    POST /numbers
numbers[1] = 23    PUT /numbers/1

但是有些资源不会在其路由中使用 id,因为要么只有一个,要么用户永远无法访问多个,所以这些不是列表:

GET /dashboard
DELETE /session
POST /session
GET /users/{:id}/profile
PUT /users/{:id}/profile

不要使用 POST /login。使用 POST /sessions 将会话添加到会话集合(有效地登录用户)并使用 DELETE /sessions 从会话集合中删除会话(有效地注销用户)
我认为将会话用于登录 POST 是有道理的,但我不同意将其多元化。您的用户/浏览器组合一次永远不能访问多个会话。你有一个,当你完成后它会被删除。无论是前端还是后端,都没有一段代码会为用户引用多个会话。这对我来说是独一无二的。
G
Guichito

我的两分钱:花时间从复数变为单数或反之亦然的方法是在浪费 CPU 周期。我可能是老派,但在我那个时代,事情被称为相同的东西。我如何查找有关人员的方法?没有一种常规的表达方式可以同时涵盖人和人而不会产生不良的副作用。

英语复数可能非常随意,它们不必要地妨碍代码。坚持一种命名约定。计算机语言应该是关于数学清晰度,而不是模仿自然语言。


这解决了尝试“自动生成/破坏”端点的代码(有许多自以为是的库假设多个/奇点并尝试映射);但是,这确实适用于明确选择的端点名称,而不是选择正确的单词(不管它是如何复数的)。
i
ivxivx

为了简单和一致,我更喜欢使用单数形式。

例如,考虑以下网址:

/客户/1

我将客户视为客户收藏,但为简单起见,收藏部分被删除。

另一个例子:

/设备/1

在这种情况下,设备不是正确的复数形式。因此将其视为设备集合并为简单起见删除集合使其与客户案例一致。


POST /customer 听起来像是要替换唯一的客户。这是我对使用单一资源名称的最大遗憾。
@andrew-t-finnell POST /customer 不应该这样做 - 插入一个客户吗?
它将单个客户插入到客户集合中。 POST /customer 对我来说就像是在向 the 客户发送邮件。不是客户的集合。但是,我承认复数或非复数是一种偏好。只要它们不像其他答案那样混合。那将是令人难以置信的混乱。
在这种情况下,“邮寄给客户”没有意义。 POST 不会替换,它会插入。也许如果它是 POST /customer/1 我可以看到两难境地,但即使从 REST 的角度来看也没有多大意义,因为你在插入什么?它将是 /customer/1/invoice 或 /customer/1/receipt 等。
因为您最终会在某个时候使用 OOP 类、验证、linting 和自动完成。在 OOP 中,您使用的类通常是单数对象,例如 Bike、User、Car……为了使类与 API 名称匹配……我使用单数。有些语言需要一个单独的单词来表示复数,这与 Child-duren 或 Child.find() 或 GET child?q="" 没有什么不同。无论如何,您都需要防止意外多重,大多数端点应该有多重......使用单数不会改变这一点。对于 REST 原生 API 来说,复数形式似乎是标准。如果休息对您的应用程序来说是次要的,那么单数会更容易。
N
Narf

使用命名约定,通常可以安全地说“只选择一个并坚持下去”,这是有道理的。

然而,在不得不向很多人解释 REST 之后,将端点表示为文件系统上的路径是最具表现力的方式。它是无状态的(文件存在或不存在)、分层、简单且熟悉 - 您已经知道如何访问静态文件,无论是在本地还是通过 http。

在这种情况下,语言规则只能让你做到以下几点:

一个目录可以包含多个文件和/或子目录,因此它的名称应该是复数形式。

我喜欢这样。虽然另一方面 - 它是您的目录,但如果您想要的话,您可以将其命名为“a-resource-or-multiple-resources”。那不是真正重要的事情。

重要的是,如果您将名为“123”的文件放在名为“resourceS”的目录下(导致 /resourceS/123),那么您不能期望它可以通过 /resource/123 访问。

不要试图让它变得比它必须的更聪明 - 根据您当前访问的资源数量从复数变为单数可能对某些人来说在美学上令人愉悦,但它不是有效的,而且它没有意义等级制度。

注意:从技术上讲,您可以制作“符号链接”,这样 /resources/123 也可以通过 /resource/123 访问,但前者仍然必须存在!


m
mtraceur

最重要的事情

每当你在接口和代码中使用复数时,问问自己,你的约定如何处理这样的词:

/pants, /eye-glasses - 这些是单数还是复数路径?

/radii - 你知道单数路径是 /radius 还是 /radix?

/index - 如果复数路径是 /indexes 或 /indeces 或 /indices,你知道吗?

理想情况下,约定应在没有不规则性的情况下扩展。英语复数不这样做,因为

它们有例外,例如以复数形式调用的东西之一,并且没有简单的算法可以从单数中获取单词的复数,从复数中获取单数,或者判断未知名词是单数还是复数。

这有缺点。我脑海中最突出的:

单数和复数形式相同的名词将强制您的代码处理“复数”端点和“单数”端点具有相同路径的情况。您的用户/开发人员必须精通英语,才能知道名词的正确单数和复数。在一个日益国际化的世界中,这可能会导致不可忽视的挫败感和开销。它单独变成“我知道 /foo/{{id}},获取所有 foo 的路径是什么?”进入自然语言问题,而不是“只删除最后一条路径”问题。

同时,一些人类语言甚至没有名词的单复数形式。他们管理得很好。你的 API 也可以。


s
seh

我不希望看到 URL 的 {id} 部分与子资源重叠,因为 id 理论上可以是任何东西并且会有歧义。它混合了不同的概念(标识符和子资源名称)。

类似问题经常出现在 enum 常量或文件夹结构中,其中混合了不同的概念(例如,当您有文件夹 TigersLionsCheetahs,然后还有一个名为 Animals 的文件夹时)同一级别——这是没有意义的,因为一个是另一个的子集)。

一般来说,我认为端点的最后命名部分应该是单数,如果它一次处理一个实体,如果它处理一个实体列表,它应该是复数。

所以处理单个用户的端点:

GET  /user             -> Not allowed, 400
GET  /user/{id}        -> Returns user with given id
POST /user             -> Creates a new user
PUT  /user/{id}        -> Updates user with given id
DELETE /user/{id}      -> Deletes user with given id

然后有单独的资源用于对用户进行查询,通常返回一个列表:

GET /users             -> Lists all users, optionally filtered by way of parameters
GET /users/new?since=x -> Gets all users that are new since a specific time
GET /users/top?max=x   -> Gets top X active users

这里有一些处理特定用户的子资源示例:

GET /user/{id}/friends -> Returns a list of friends of given user

结交朋友(多对多链接):

PUT /user/{id}/friend/{id}     -> Befriends two users
DELETE /user/{id}/friend/{id}  -> Unfriends two users
GET /user/{id}/friend/{id}     -> Gets status of friendship between two users

从来没有任何歧义,资源的复数或单数命名是向用户暗示他们可以期待什么(列表或对象)。对 id 没有限制,理论上可以让具有 id new 的用户不与(潜在的未来)子资源名称重叠。


在您的示例中,您希望 GET /user/{id}/friend 代表什么?我想确保如果您删除 URL 的一部分,仍然会返回资源,继续您的示例,我假设(正确或错误)这将返回用户 {id} 的所有朋友,但这与您使用复数和名词。
复数版本在答案 /user/{id}/friends 中,它将返回所有朋友。单数版本 /user/{id}/friend 将是一个错误的请求 400,就像 /user
S
Samuel Danielson

使用 Singular 并利用例如“Business Directory”中看到的英语惯例。

很多东西都是这样读的:“书柜”、“狗包”、“美术馆”、“电影节”、“车场”等。

这方便地匹配从左到右的 url 路径。左侧的项目类型。在右侧设置类型。

GET /users 真的会获取一组用户吗?通常不会。它获取一组包含密钥和用户名的存根。所以它不是真的 /users 反正。它是一个用户索引,或者如果你愿意的话,它是一个“用户索引”。为什么不这么称呼它?这是一个/user/index。由于我们已经命名了集合类型,我们可以有多种类型来显示用户的不同投影,而无需使用查询参数,例如 user/phone-list/user/mailing-list

那么用户 300 呢?它仍然是 /user/300

GET /user/index
GET /user/{id}

POST /user
PUT /user/{id}

DELETE /user/{id}

最后,HTTP 只能对单个请求进行单个响应。路径总是指一个单一的东西。


i
ivanleoncz

这是“Architectural Styles and the Design of Network-based Software Architectures”的 Roy Fielding 论文,您可能会对此引用感兴趣:

资源是到一组实体的概念映射,而不是在任何特定时间点对应于映射的实体。

作为一种资源,映射到一组实体,对我来说似乎不符合逻辑,使用 /product/ 作为访问产品集的资源,而不是 /products/ 本身。如果您需要特定产品,则可以访问 /products/1/

作为进一步的参考,这个来源有一些关于资源命名约定的文字和例子:

https://restfulapi.net/resource-naming/


P
Paulo Merson

至少在一个方面对所有方法使用复数更实用:如果您正在使用 Postman(或类似工具)开发和测试资源 API,则在从 GET 切换到 PUT 到 POST 等时不需要编辑 URI .


这对我来说不是一个争论,因为 Postman 提供集合,因此您可以将所有资源保存为不同的集合项并单独测试它们。您所做的只是从集合中选择资源,您不必每次都编辑参数/方法/等。
w
wynnset

我知道大多数人在决定是使用复数还是单数之间犹豫不决。这里没有解决的问题是客户需要知道您使用的是哪一个,而且他们总是可能犯错误。这就是我的建议的来源。

两者怎么样?我的意思是对整个 API 使用单数,然后创建路由以将复数形式的请求转发到单数形式。例如:

GET  /resources     =     GET  /resource
GET  /resources/1   =     GET  /resource/1
POST /resources/1   =     POST /resource/1
...

你得到图片。没有人是错的,只需最少的努力,客户总能做对。


如果您正在执行 302 重定向并且您的缓存将所有内容存储两次,那么您的缓存设置错误。缓存不应该存储 302 重定向。
如果您的客户总是使用 /resources 并且总是被重定向到 /resource,那么您做错了。如果其他人使用您的 API,他们可以直接使用正确的 URL 或被重定向(这有效但错误),是您打开了错误的方式。
不确定您所说的“错误”是什么意思-这是非常主观的。这并不是真正的错误,因为它确实有效。
这增加了维护成本、理解成本以及所需的代码量。
S
Steve Buzonas

两种表示方式都很有用。为了方便起见,我使用单数已经有一段时间了,变形可能很困难。我在开发严格单一的 REST API 方面的经验,使用端点的开发人员对结果的形状缺乏确定性。我现在更喜欢使用最能描述响应形状的术语。

如果你所有的资源都是顶级的,那么你可以摆脱单一的表示。避免拐点是一大胜利。

如果您正在执行任何类型的深度链接来表示对关系的查询,则可以通过更严格的约定来帮助针对您的 API 编写代码的开发人员。

我的约定是 URI 中的每个深度级别都在描述与父资源的交互,并且完整的 URI 应该隐含地描述正在检索的内容。

假设我们有以下模型。

interface User {
    <string>id;
    <Friend[]>friends;
    <Manager>user;
}

interface Friend {
    <string>id;
    <User>user;
    ...<<friendship specific props>>
}

如果我需要提供一个资源来允许客户获取特定用户的特定朋友的经理,它可能看起来像:

GET /users/{id}/friends/{friendId}/manager

以下是更多示例:

GET /users - 列出全局用户集合中的用户资源

POST /users - 在全局用户集合中创建一个新用户

GET /users/{id} - 从全局用户集合中检索特定用户

GET /users/{id}/manager - 获取特定用户的经理

GET /users/{id}/friends - 获取用户的好友列表

GET /users/{id}/friends/{friendId} - 获取用户的特定朋友

LINK /users/{id}/friends - 向该用户添加好友关联

UNLINK /users/{id}/friends - 删除该用户的好友关联

请注意每个级别如何映射到可以操作的父级。对同一个对象使用不同的父对象是违反直觉的。在 GET /resource/123 检索资源并没有表明应该在 POST /resources 创建新资源


r
ruralcoder

对我来说,复数操纵集合,而单数操纵集合中的项目。

集合允许方法 GET / POST / DELETE

项目允许方法 GET / PUT / DELETE

例如

/students 上的 POST 将在学校添加一个新学生。

/students 上的 DELETE 将删除学校中的所有学生。

/student/123 上的 DELETE 将从学校删除学生 123。

这可能感觉不重要,但有些工程师有时会忘记 id。如果路由始终是复数并执行了 DELETE,您可能会不小心擦除数据。而缺少单数上的 id 将返回未找到的 404 路由。

如果 API 应该公开多所学校,为了进一步扩展示例,那么类似

/school/abc/students 上的 DELETE 将删除学校 abc 中的所有学生。

有时选择正确的词本身就是一个挑战,但我喜欢保持收藏的多样性。例如 cart_itemscart/items 感觉不错。相反,删除 cart 会删除它自己的购物车对象,而不是购物车中的项目;)。


无论如何,这不应该是 /cart 和 /cart/item(s) 吗?然后您可以处理整个购物车(例如删除)或单个项目?
@RobertGrant 那不是“/carts/items/123”吗? (例如,为什么“购物车”而不是“购物车”的规则是“总是复数”?)
我认为,如果签入能够删除每个人的购物车项目的生产代码,则存在比命名约定更大的问题。他们在 ID 上记住“s”的可能性要小得多。
有人会创建一个简单地删除整个集合的端点吗?对我来说似乎非常危险,可能也是为什么 REST 并不真正支持批量删除。 (你必须将数组包装成一个对象)。如果我绝对需要一个端点来删除整个集合,我会确保 URI 非常独特,并且绝对不同于 POST
k
kmjb

关于这个问题的很好的讨论点。根据我的经验,命名约定或者不建立本地标准是许多长夜待命、头痛、风险重构、狡猾的部署、代码审查辩论等的根本原因。特别是当它决定事情需要改变是因为一开始没有充分考虑。

一个实际问题跟踪了对此的讨论:

https://github.com/kubernetes/kubernetes/issues/18622

看到这方面的分歧很有趣。

我的两分钱(带有轻微的头痛经验)是,当您考虑用户、帖子、订单、文档等常见实体时,您应该始终将它们视为实际实体,因为这是数据模型的基础。语法和模型实体不应该在这里真正混淆,这会导致其他混淆点。然而,一切都是非黑即白的吗?确实很少如此。上下文真的很重要。

当您希望获取系统中的用户集合时,例如:

获取 /user ->实体用户集合

获取 /user/1 ->实体用户资源:1

说我想要一个实体用户集合和说我想要用户集合都是有效的。

获取 /users ->实体用户集合

获取 /users/1 ->实体用户资源:1

从这里你说,从用户集合中,给我用户/1

但是,如果您分解用户集合是什么... 它是一个实体集合,其中每个实体都是一个 User 实体。

您不会说实体是 Users,因为单个数据库表通常是 User 的单独记录。但是,我们在这里讨论的是 RESTful 服务而不是数据库 ERM。

但这仅适用于具有明确名词区别的用户,并且易于掌握。但是,当您在一个系统中使用多种相互冲突的方法时,事情会变得非常复杂。

说实话,这两种方法在大多数情况下都是有意义的,除非在少数情况下英语只是意大利面条。它似乎是一种迫使我们做出许多决定的语言!

事情的简单事实是,无论您做出什么决定,都要保持一致和合乎逻辑的意图。

在我看来,在这里混合是一种不好的方法!这悄悄地引入了一些可以完全避免的语义歧义。

看似单一的偏好:

https://www.haproxy.com/blog/using-haproxy-as-an-api-gateway-part-1/

这里有类似的讨论:

https://softwareengineering.stackexchange.com/questions/245202/what-is-the-argument-for-singular-nouns-in-restful-api-resource-naming

这里的首要常数是,根据大公司指南中的细节,它确实似乎确实取决于某种程度的团队/公司文化偏好,两种方式都有许多优点和缺点。谷歌不一定对,只是因为它是谷歌!这适用于任何准则。

避免把你的头埋在沙子里太多,松散地建立你对轶事例子和观点的整个理解系统。

您是否必须为所有事情建立可靠的推理。如果它适合您或您的团队和/我们的客户,并且对新的和经验丰富的开发人员有意义(如果您处于团队环境中),那就太好了。


c
chrisyue

怎么样:

/resource/(不是 /resource

/resource/ 表示它是一个包含名为“resource”的文件夹,它是一个“resouce”文件夹。

而且我认为数据库表的命名约定是相同的,例如,一个名为“用户”的表是一个“用户表”,它包含一个名为“用户”的东西。


p
pberggreen

我更喜欢同时使用复数 (/resources) 和单数 (/resource/{id}),因为我认为它更清楚地区分了处理资源集合和处理单个资源之间的逻辑。

作为一个重要的副作用,它还可以帮助防止有人错误地使用 API。例如,考虑用户错误地尝试通过将 Id 指定为参数来获取资源的情况,如下所示:

GET /resources?Id=123

在这种情况下,如果我们使用复数版本,服务器很可能会忽略 Id 参数并返回所有资源的列表。如果用户不小心,他会认为调用成功并使用列表中的第一个资源。

另一方面,当使用单数形式时:

GET /resource?Id=123

服务器很可能会返回错误,因为没有以正确的方式指定 Id,并且用户将不得不意识到有问题。


你为什么在这里混合成语?您在第一段中使用了正确的 URI 表示法,然后切换到查询参数?使用查询参数来获取 ID 为 123 的资源在这里完全不适用。
那显然是一个错误。我现在已经更新了我的答案。感谢您注意到它。
在再次被否决后,我查看了我写的内容,我意识到原来的帖子是正确的。我的意思是,如果用户做错了事,那么使用复数+单数实际上会给出比仅使用复数更好的错误信息。
我仍然觉得这使手头的问题感到困惑。使用复数的想法是它是一个集合。最后的数字是集合的索引。如果你自己 GET /resource 怎么办?同时使用复数和单数是相当混乱的。说 /resources/123 说:在资源桶中获取我的资源 123。