REST API设计:嵌套集合与新根目录

Ale*_*lex 30 rest api-design conceptual

这个问题是关于最佳REST API设计和我在嵌套资源和根级别集合之间选择的问题.

为了说明这个概念,假设我有收藏City,BusinessEmployees.典型的API可以如下构造.想象一下,ABC,X7N和WWW是关键,例如guids:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/City/ABC/Businesses/X7N                   (returns business X7N)
GET Api/City/ABC/Businesses/X7N/Employees         (returns all employees at business X7N)
PUT Api/City/ABC/Businesses/X7N/Employees/WWW     (updates employee WWW)
Run Code Online (Sandbox Code Playgroud)

这看起来很干净,因为它遵循原始域结构 - 业务在城市,员工在公司.单个项目可通过集合下的密钥访问(例如,../Businesses返回所有业务,同时../Businesses/X7N返回单个业务).

以下是API消费者需要做的事情:

  • 在城市中获得业务 (GET Api/City/ABC/Businesses)
  • 让所有员工都参与进来 (GET Api/City/ABC/Businesses/X7N/Employees)
  • 更新个人员工信息 (PUT Api/City/ABC/Businesses/X7N/Employees/WWW)

第二次和第三次调用虽然看起来在正确的位置,但使用了许多实际上不必要的参数.

  • 为了让员工参与到业务中,唯一需要的参数是business(X7N)的关键.
  • 要更新单个员工,唯一的参数需要员工的密钥(WWW)

后端代码中的任何内容都不需要非关键信息来查找业务或更新员工.因此,相反,以下端点看起来更好:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Businesses/X7N/Employees                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我已经为企业和员工创建了一个新的,即使从域的角度来看,它们也是一个子/子子集合.

这两种解决方案对我来说都不是很干净.

  • 第一个例子要求提供不必要的信息,但结构的方式对消费者来说是"自然的"(来自集合的单个项目通过较低的叶子检索)
  • 第二个例子只询问必要的信息,但不是以"自然"的方式构建 - 子集可以通过根访问
  • 添加新员工时,单个员工root将无法工作,因为我们需要知道要将员工添加到哪个业务,这意味着调用至少必须驻留在Business根目录下,例如POST Api/Businesses/X7N7/Employees,这会使一切更加混乱.

有没有更清洁,第三种方式,我没有想到?

eds*_*ufi 22

我没有看到REST如何添加一个约束,即两个资源不能具有相同的值.这resourceType/ID只是最简单的用例的一个例子,而不是从RESTful角度出发的最佳方式.

如果您仔细阅读Roy Fielding论文的第5.2.1.1段,您会注意到Fielding在价值资源之间进行了区分.现在资源应该有一个唯一的URI,这是真的.但没有什么能阻止两个资源具有相同的价值:

例如,学术论文的"作者'首选版本"是其值随时间变化的映射,而对"在会议X的会议论文中发表的论文"的映射是静态的.这是两个不同的资源,即使它们在某个时间点都映射到相同的值.区分是必要的,以便可以独立地识别和引用这两种资源.软件工程中的一个类似示例是在引用"最新版本","版本号1.2.7"或"橙色版本中包含的修订版"时单独标识版本控制的源代码文件.

所以没有什么可以阻止你,正如你所说,改变根.在您的示例中,a Business是值而不是资源.创建一个资源是完美的RESTful,这是一个"位于城市中的每个企业"的列表(就像Roy的例子,"橙色版本中包含的修订"),同时拥有"ID为x的资源"资源(如"修订号x").

因为Employees,我会保持API/Businesses/X7N/Employees一个企业和它的员工之间的关系是一个组合关系,因此,正如你所说,Employees可以而且应该只能通过Businesses类根访问.但这不是REST要求,另一种选择也是RESTful.


请注意,这与HATEAOS原则的应用是一致的.在您的API中,位于城市中的企业列表可以(也许应该从理论的角度来看)只是一个链接列表API/Businesses.但这意味着客户端必须为列表中的每个项目进行一次往返服务器.这样做效率不高,为了保持务实,我所做的就是将业务的表示形式与self此示例中的URI链接一起嵌入到列表中API/Businesses.


nep*_*dev 13

您不应该将REST与特定URI命名约定的应用程序混淆.

如何命名资源完全是次要的.您正在尝试使用HTTP资源命名约定 - 这与REST无关.罗伊·菲尔丁本人在其他人引用的文件中反复说明.REST不是协议,它是一种架构风格.

事实上,Roy Fielding在2008年的博客评论中指出(http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven 6/20/2012):

"REST API不能定义固定资源名称或层次结构(客户端和服务器的明显耦合).服务器必须能够自由控制自己的命名空间.相反,允许服务器指示客户端如何构造适当的URI,例如通过在媒体类型和链接关系中定义这些指令,在HTML表单和URI模板中完成."

所以本质上:

您描述的问题实际上并不是REST的问题 - 从概念上讲,它是层次结构与关系结构的问题.

虽然企业"在"一个城市,因此可以被视为城市"等级"的一部分 - 那么在75个城市设有办事处的国际公司呢?然后,城市突然成为层次结构中的初级元素,其中商业名称位于结构的高级层次.

关键是,您可以从各个角度查看数据,并且根据您所采用的视点,将其视为层次结构可能最简单.但是相同的数据可以看作是具有不同级别的层次结构.使用HTTP类型资源名称时,您已输入HTTP定义的层次结构.这是一个约束,是的,但它不是REST约束,它是HTTP约束.

从这个角度来看,您可以选择更适合您场景的解决方案.如果您的客户在提供公司名称(他可能不知道)时无法提供城市名称,那么最好只使用城市名称的密钥.正如我所说,这取决于你,REST不会阻挡你...

更重要的是:

如果您已经决定将HTTP与GET PUT一起使用,那么您拥有的唯一真正的REST约束是:

    1. 您不能在客户端和服务器之间预先知道任何先前的("带外")知识.*

Look at your proposal #1 above in that light. You assume that customers know the keys for the cities which are contained in your system? Wrong - that's not restful. So the server has to give the list of cities as a list of choices in some way. So are you going to list every city in the world here? I guess not, but then you'll have to do some work on how you are planning to do this, which brings us to:

    1. A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state ...

I think, reading the mentioned Roy Fielding blog will help you out considerably.


xwo*_*ker 5

在 RESTful-API URL 设计中应该是不重要的——或者至少是一个附带问题,因为可发现性是在超文本中编码的,而不是在 URL 路径中。在 StackOverflow 上查看REST 标签 wiki 中链接的资源。

但是如果你想为你的 UC 设计人类可读的 URL,我建议如下:

  1. 使用您正在创建/更新/查询的资源类型作为 URL 的第一部分(在您的 API 前缀之后)。因此,当有人看到 URL 时,他立即知道此 URL 指向哪些资源。 GET /Api/Employees...是从 API 接收员工资源的唯一方法。

  2. 为每个资源使用唯一 ID,独立于它们所处的关系。所以GET /Api/<CollectionType>/UniqueKey应该返回一个有效的资源表示。没有人应该担心 Employee 的位置。(但返回的 Employee 应该有他所属的 Business(为了方便起见)的链接。)GET /Api/Employees/Z6W无论位于何处,都返回具有此 ID 的 Employee。

  3. 如果您想获取特定资源:将您的查询参数放在最后(而不是按照问题中描述的层次结构顺序)。您可以使用 URL 查询字符串 ( GET /Api/Employees?City=X7N) 或矩阵参数表达式 ( GET /Api/Employees;City=X7N;Business=A4X,A5Y)。这将使您能够轻松地表达特定城市中所有员工的集合 - 独立于他们所在的业务。

侧节点:

根据我的经验,初始的分层域数据模型很少能满足项目期间出现的额外要求。在您的情况下:考虑位于两个城市的企业。您可以通过将其建模为两个独立的业务来创建解决方法,但是如果员工一半时间在一个地方工作,另一半时间在另一个地点工作呢?甚至更糟:他只清楚他在哪个城市工作,但没有明确定义?