RESTful API 做同一件事的不同方式

Posted

技术标签:

【中文标题】RESTful API 做同一件事的不同方式【英文标题】:RESTful API different ways of doing same thing 【发布时间】:2019-06-16 04:42:52 【问题描述】:

RESTful API 设计似乎有些主观,但我想要一些关于以下方法的反馈以及它们的优缺点。一个对吗?都错了吗?两者都可以提供(即使它会提供部分重复的功能)?

GET 被多个 ID 过滤...

以下是一些看起来相当正常的方法(?f= 只是说明过滤器/搜索参数的可选使用):

GET /supplier/supplierId/products?f=...
GET /oem/oemId/products?f=...

获取产品的 URL 是否应该超过 1 个?另外,如果我想同时过滤供应商和 oem 怎么办?我应该只有一个 /products 端点并且一切都是过滤器,还是只能从供应商/oem 之一访问产品,但不能同时访问两者,或者将它们全部放在一起?:

GET /products?supplier=supplierId&oem=oemId&f=...
GET /supplier/supplierId/products?oem=oemId&f=...
GET /oem/oemId/products?supplier=supplierId&f=...

获取不按父 ID 过滤的子集合...

这是一种看起来相当正常的方法:

GET /customers/customerId/addresses?f=...

但是,如果我需要所有客户的订单怎么办。这两种方法都合理吗?

GET /customers/addresses?f=...
GET /addresses?f=...

/customers/addresses URL(没有 customerId)似乎在概念上有意义,但我没有看到我一直在寻找的 RESTful 设计文档中使用的方案(总是存在 id) . /addresses URL 很简单,但破坏了客户地址层次结构。

我认为它真的没有那么重要,只要它对使用 API 的每个人都有意义并且是一致的,但我想知道其他人是如何处理这些类型的以“行业标准”的方式解决问题。谢谢。

【问题讨论】:

【参考方案1】:

正如您正确提到的,实现这一目标不一定有正确或错误的方法。

就个人而言,我喜欢从资源角度考虑端点,并在GET 上应用相关过滤器来过滤结果。有时,如果有意义,我会添加 GET /search/<sub-resource> 以方便在更广泛的上下文中搜索子资源。

关于您的第一个示例,如果您的主要资源是 product,而 oemsupplier 只是产品的属性(仅与产品相关),则使用 /product?supplier=..&oem=... 过滤基于oem 和/或supplier 的产品可以使用。您可以使用GET /supplierGET /oem 分别返回有关供应商和OEM 的详细信息,但不一定是产品本身。

或者,如果您认为两者都只是子资源,您可以使用GET /search/oem?product_id=...&customer_id=..根据根资源搜索oem,例如productcustomer

关于为客户检索ordersGET /customers/customerId/orders?f=... 对单个客户有意义。要为所有客户退货,您可以使用/orders?customerId=...

同样,如果order 仅被视为客户的子资源,您也可以使用GET /search/orders?customerId=..

同样,以上只是建议。你提议的也可以。

【讨论】:

【参考方案2】:

我在这方面没有丰富的经验,因为我只写过少数服务。然而,在开发它们时,我总是发现最好考虑到消费者。消费者对什么感兴趣?在您的第一个示例中,它看起来是产品,我会将供应商和 oem 视为特定的过滤器。就个人而言,我可能会采用以下方式:

GET: /products?filters(包括供应商和/或 oem)

关于第二部分,我再次建议与消费者合作,看看什么是重要的,什么对他们有用。他们是对订单特别感兴趣,还是对客户更感兴趣并需要获取特定客户的订单详细信息和地址?

假设是后者,那么我可能会选择以下内容:

GET: /customers - 获取所有客户的基本信息(ID、姓名等) GET: /customers/customerId - 获取特定客户的基本信息(ID、姓名等) GET: /customers/customerId/orders - 获取基本订单信息(可能是订单号、创建日期?) GET: /customers/customerId/orders/orderId - 获取有关特定订单的详细信息。 GET: /customers/customerId/addresses - 获取与客户关联的所有地址。 GET: /customers/customerId/addresses/addressId - 获取与客户关联的特定地址。

您总是可以有多个路由到同一个资源,具体取决于上下文:

GET: /customers/customerId/addresses/123 - 返回 id 为 123 的地址 GET: /addresses/123 - 返回 id 为 123 的地址

归根结底,这一切都归结为 API 的使用者希望如何获取信息,以及什么对他们有意义。

【讨论】:

【参考方案3】:

REST 没有严格的规则,因此解决方案在某种意义上是主观的。 我会看看一些大型 API REST 提供商如何解决这个问题。 我相信探索 Etsy 或 Fitbit 的 REST API 会为您带来一些见解。

GET 被多个 ID 过滤

对于您的第一种情况,我会考虑创建一个聚合服务,该服务对 /supplier/supplierId/products?f=/oem/oemId/products?f= 进行 2 次并行调用并合并结果集。它基于 Etsy 对他们的 concept of "bundles" 所做的事情。

您可以让它接受filters=[key=value,key=value] 形式的过滤器列表,但自然这只支持过滤器的结合。

获取不按父 ID 过滤的子集合

Google 和其他公司都在使用“-”作为所有概念。

例如:

/customers/customerId/addresses 是单个客户的所有地址

/customers/-/addresses 是所有客户的所有地址

如果您想保留一些层次结构(例如,在您的域中,您有员工和客户,并且两个组都有地址),这将非常有用。 那么你可能会说:

/customers/-/addresses
/employees/-/addresses

如果您不需要分层特征,那么您可能只需为所有地址公开一个专用端点(只需 /addresses)。

【讨论】:

【参考方案4】:

我也将对此稍有不同的看法。

您主要讨论的是资源集合,而不是该集合中的单个资源。

一般来说,我喜欢确保每个单独的资源(例如产品)都有自己唯一的 url。例如/products/1234

但是,一个产品可能会出现在多个集合中。集合并不真正“拥有”或“包含”集合中的项目,它只是链接到它。因此,一种思考方式是,可以有许多包含相同产品的集合。

您如何知道是否是同一产品出现在多个系列中?你附上你正在嵌入的产品的 uri,所以很清楚这些集合实际上都链接到同一个东西。

这与文件系统非常相似。许多目录可以包含相同的(硬链接或软链接)文件。

来源:

http://stateless.co/hal_specification.html https://evertpot.com/rest-embedding-hal-http2/(我自己的博客)

【讨论】:

以上是关于RESTful API 做同一件事的不同方式的主要内容,如果未能解决你的问题,请参考以下文章

试图理解在 Java 中声明对象的不同方式? [复制]

模板方法设计模式

模板方法设计模式

做好一件事的三要素

有没有人以这种方式设计 api 或库代码?

django中要指定时间做某件事的代码怎么弄?