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

Posted

技术标签:

【中文标题】我应该对 REST 资源使用单数还是复数命名约定?【英文标题】:Should I use Singular or Plural name convention for REST resources? 【发布时间】:2011-10-14 07:37:25 【问题描述】:

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

创建 - 在某些地方使用 /resources 和 POST 方法(观察复数)使用 /resource(单数) 更新 - 使用 /resource/123 和 PUT 方法 Get - 使用 /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 【参考方案1】:

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

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

使用/resource 而不是/resources 与使用文件系统和文件集合的方式类似,而/resource 是个人@987654329 的“目录” @, 456 里面有文件。

没有对错,选择你最喜欢的。

【讨论】:

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

我也没有看到这样做的意义,我认为这不是最好的 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 等时,这两种不同资源中的每一种都代表完全不同的结果。【参考方案3】:

而最普遍的做法是使用复数形式的 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。不过,自动化文档的影响是真实的。【参考方案4】:

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

Table Naming Dilemma: Singular vs. Plural Names

【讨论】:

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

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

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 实体。并且数据库标准倾向于提倡单一的表名,所以我们会这样做,但仍然与这个答案的逻辑相同。【参考方案6】:

从 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/&lt;id&gt; 将创建一个新用户。 @TomRussell 通常是服务器创建 id,所以你还不知道要 POST 到的 id。 @TomRussell,当客户端在创建新资源时确定(一种)id时,更常见的是使用PUT /users/&lt;id&gt;而不是POSTPOST 的解释是“将其添加到集合中,并将 id 确定为其中的一部分”。 PUT 的解释是“用这个 id 更新(或添加)这个资源”。有关此原理的详细说明,请参阅restcookbook.com/HTTP%20Methods/put-vs-post。 @DaBlick - 你能找到你的“最佳实践”来源吗?【参考方案7】:

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

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

【讨论】:

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

复数

简单 - 所有网址都以相同的前缀开头 逻辑 - 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 你可以使用单数形式来标记这个单个对象(例如头像) - 更详细的例子在这里:***.com/a/38296217/860099。顺便说一句-非常好的答案:) 类名和表名的映射如何,应该是单数的? (见other answer) @WillSheppard - 类名最好采用单数形式,表名最好采用复数形式。例如,Order 是一个类的好名称,该类处理引用一个订单的对象的单个实例。 OrderList 是处理多个 Order 实例的类的名称。 Orders Table 是多订单数据库表的好名字。 我想获取 /orders 但我只想要 /1 @jim-smith 那你为什么不使用 GET /users/1 从用户集合中请求 /1?【参考方案9】:

单数

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

例如客户地址优于客户地址

考虑这个相关的资源。

这个/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/fishid。由于使用了也可能是古老的集体名词,分组时也存在问题:/murders/murderid/crowid; /gaggles/gaggleid/gooseid。所以复数也可以复数。 “简单的标准规则”永远不会奏效,规则与某处语言的“自然”人类表达性之间总会存在不匹配。真正的问题是 a) 是否接受笨拙的 uri 设计作为事实上的标准 b) 拒绝粗暴和过于简单的“标准约定”。 @Koray Tugay 裤子很有趣,因为它们在历史上被认为是一对(每条腿一个),在历史上并不总是需要在顶部连接。所以它们更像是一双袜子或鞋子。【参考方案10】:

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

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

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

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

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

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

不要试图让它变得比它必须的更聪明 - 根据您当前访问的资源数量从复数变为单数可能对某些人来说在美学上令人愉悦,但它不是有效的,它不会使分层系统中的意义。

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

【讨论】:

【参考方案11】:

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

【讨论】:

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

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

例如,考虑以下 url:

/客户/1

我会把customer当成customer collection,但是为了简单起见,把collection部分去掉了。

另一个例子:

/设备/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 来说,复数形式似乎是标准。如果休息对您的应用程序来说是次要的,那么单数会更容易。【参考方案13】:

对我来说,复数操纵集合,而单数操纵item在该集合中。

Collection 允许方法 GET / POST / DELETE

Item 允许方法 GET / PUT / DELETE

例如

/students 上发布将在学校添加一名新学生。

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

/student/123 上删除会将学生 123 从学校中删除。

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

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

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

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

【讨论】:

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

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

看 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)

【讨论】:

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

这两种表示方式都很有用。为了方便起见,我使用单数已经有一段时间了,变形可能很困难。我在开发严格单一的 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 完成创建新资源

【讨论】:

【参考方案16】:

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

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

GET /resources?Id=123

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

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

GET /resource?Id=123

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

【讨论】:

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

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

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

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

你明白了。没有人是错的,只需极少的努力,客户总会做对的。

【讨论】:

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

怎么样:

/resource/(不是/resource

/resource/ 表示它是一个文件夹,包含一个叫做“资源”的东西,它是一个“资源”文件夹。

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

【讨论】:

【参考方案19】:

路由中的 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 从会话集合中删除会话(有效地注销用户) 我认为使用 session 进行登录 POST 是有道理的,但我不同意将其复数化。您的用户/浏览器组合一次永远不能访问多个会话。你有一个,当你完成后它会被删除。无论是前端还是后端,都没有一段代码会为用户引用多个会话。这对我来说是独一无二的。【参考方案20】:

请参阅 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   |
|--------------------------+---------------+-------------------+---------------+--------------|

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

【讨论】:

【参考方案21】:

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

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

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

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

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

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

【讨论】:

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

使用 Singular 并利用在例如“企业名录”。

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

这很方便地匹配从左到右的 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 只能对单个请求进行单个响应。路径总是指一个单一的东西。

【讨论】:

【参考方案23】:

最重要的事情

每当您在接口和代码中使用复数形式时,问问自己,您的惯例如何处理这些词:

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

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

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

理想情况下,会议应在没有不规则的情况下扩大规模。英语复数不这样做,因为

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

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

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

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

【讨论】:

【参考方案24】:

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

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

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

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

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

【讨论】:

【参考方案25】:

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

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

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

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

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

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

GET /user -> 实体用户集合

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

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

GET /users -> 实体用户集合

GET /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

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

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

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

【讨论】:

以上是关于我应该对 REST 资源使用单数还是复数命名约定?的主要内容,如果未能解决你的问题,请参考以下文章

数据库、表和列的命名约定? [关闭]

ASP.NET MVC 控制器命名多元化

父母的英文parent应该用单数还是复数

单数还是复数数据库表名? [复制]

resource啥时候用单数?啥时候用复数?

java编程中,写一个工具类,命名时使用单数util还是使用复数utils好呢?