RESTful URL 设计:公共与私有 API、分层 API 设计模式、URI 与 URL 设计?
Posted
技术标签:
【中文标题】RESTful URL 设计:公共与私有 API、分层 API 设计模式、URI 与 URL 设计?【英文标题】:RESTful URL design: public vs private API, hierhachy API design pattern, URI vs URL design? 【发布时间】:2013-12-19 23:00:23 【问题描述】:我经常遇到这样的问题,与Hierarchical RESTful URL design非常相似
假设该服务仅提供用户上传文档。
POST, GET /accounts
PUT, DELETE /accounts/name or /accounts/id
现在一个文档被附加到一个特定的用户,不管它是不是公开的,这里都不关心。
两种方式是POST /documents vs POST /users/documents
为什么?因为稍后创建文档资源时,该文档处于用户的控制之下。所以我希望有
GET, PUT, DELETE /users/name/documents
用于获取、更改和删除用户拥有的一堆文档。
我可以有GET, PUT, DELETE /users/name/documents/name/id
但是/documents/users/.... or /documents/id
也可以达到同样的效果。这类似于组织 unix 文件的方式(尽管/users/...
也是组织文件的另一种方式......)你看,还有一种哲学[phy of uri vs url design。
另一个考虑因素是 API 是否对用户可见。如果这只是一个后端 API,只有开发人员可以访问(backend <- frontend server <- frontend ajax)
,那么网站用户可能会更喜欢/users/name/documents/id/name
,而如果 API 是公开的(如 twitter api),一些程序员会不喜欢这个长 url。
人们如何看待这些问题?
【问题讨论】:
【参考方案1】:从务实的角度来看,显然以上所有内容都是正确的。
但从哲学上讲,我认为它归结为 REST 中的“S”......这实际上是一个问题,即您管理状态的资源实际上是什么。如果您的应用程序旨在处理文档,那么这需要是 URL 中明显的资源。如果您的应用更多地是关于用户执行某些工作流,那么您可能希望让用户在 URL 中显而易见。
在应用程序内部,为了方便起见,事情很快就会建立起来,这样您就可以拥有混合和匹配资源的 URL,以使所有权明显(如您的示例中所示)。这样想吧。用户使用您的应用来处理他们的东西,其中一些是文档。他们知道他们已登录,然后他们可以访问他们的文档,他们的一些信息在会话中与他们一起传播,并且从这个角度来看
user/name/documents
有道理。他们有上下文。
如果您将该 API 公开...也就是说,如果与您的应用程序中的用户没有相同观点的人正在使用您的 API...那么该公开面孔需要在语义上清晰 - 是用户裸露?文件?消费者是否关心谁拥有该文件?该 API 就像一个目录;应该会有帮助的。
因此,如果您的 api 与其他应用程序的合同是您的应用程序正在公开文档,那么我会说您最好在您的 URL 中明确这一点。到用户的路由的概念在这里没有意义,因为上下文与合同无关。
你提出了一个有趣的例子,非常清楚地表明了这一点。采取
document/id
与 document/name
文件的命名可以遵循约定或特定于应用程序。很好的例子是图像共享应用程序或特定行业的应用程序,如会计。但是公共 API 不能假定命名约定是显而易见的(除非您允许 API 的使用者访问此类信息)。因为它是一个面向公众的 API,所以最好还是使用
document/id
因为 id 通常被认为是不可变的,即使跨 API 也是如此,并且语义非常清晰。
最终,这项技术可以做我们想做的任何事情。但是路由和 URL 之类的东西对于理解 API 本身的语义很重要。如果您正在管理某事,那么在使用 API 时这应该是显而易见的,并且不应该被您的本地或技术特定的约定所困扰。
【讨论】:
【参考方案2】:好的,您无疑会遇到、研究或偶然发现一些约定。然而,虽然我在这里可能不是 100% 正确,但现在暂时搁置惯例。
在编写 RESTful 服务时,首先要考虑流程的层次结构以及谁拥有什么。换一种说法。如果我想删除属于一组人的文档,我不会写像 /user/doc/id 这样的 url。 /user/doc/id 之类的东西通常在逻辑上意味着您首先处理与用户相关的内容,然后是文档,然后是该用户的特定文档。
所以我想说的是,我通常会尝试将 GET、POST、PUT、DELETE 方法保留给实体,并将 URL 与实体的层次结构相关联。它是否 100% 有效,不,但它是否让任何在我身后的编码人员都很明显,几乎总是答案是肯定的。
在我看来,您的 /documents 与 /user/docs 示例意味着您有两个不同的文档。超出用户寿命的文档和将由用户帐户生存和死亡的文档。如果在我的示例中您有 /documents/usr/id 那么我会说即使您使用 /documents/usr/id “删除”该文档我会说您只是删除该用户引用该文档作为文档在层次结构中高于用户。同样,如果您有 /user/document/id,我会说该文档是特定于用户的,并且会随着用户而死。
希望这是有道理的。再说一次,并不是说我是 100% 正确的,而且总是有特殊情况。
【讨论】:
【参考方案3】:我在这里没有看到任何问题。 REST 没有 URI 约束,这是一个实现细节,完全取决于服务开发人员。
REST 是关于 M2M 通信而不是 H2M 通信,因此最终用户不会看到任何关于 REST URI 的信息。
如果您应用 HATEOAS 约束,客户端也不关心 URI。如果您不喜欢超媒体响应而更愿意记录 URI 模板,那么我认为较短的 URI 更好,因为客户端开发人员更容易误输入。
关闭:
我目前正在阅读 Vernon 的书中关于 DDD 聚合的内容,这里有一个有趣的相似之处。如果您想访问用户文档,那么您有 2 个建模选项。
第一个选项将文档放在用户聚合后面,所以你必须使用user1 = userRepo.findById(1); user1docs = user1.documents
使用用户聚合来访问它们。
定义文档聚合的第二个选项:user1docs = docRepo.findByUserId(1)
。
这有点类似于users/1/docs
vs docs?user=1
。
通过 DDD,解决方案取决于业务需求。如果您在某些情况下需要用户与其文档之间的事务(即时)一致性,那么第一个是不错的选择,否则您可以坚持最终一致性和第二个选项。 也许这也可以应用于 REST。因此,如果文档和用户属性之间存在不变量,那么您必须使用
PUT /users/1
prop: 123,
docs: [
,
,
...
]
以避免不一致的状态。否则最好坚持以下几点:
PUT /users/1
prop:123
POST /docs/ [
,
,
]
POST /user-docs/ [
user:1, doc: 1,
,
,
...
]
很难找到任何同时依赖于用户属性和用户文档的验证规则,所以我可能会选择第二个选项。
办公室。此规则可能存在例外情况,例如,如果第二个选项速度慢得令人无法接受,或者您想轻松地将用户文档添加到客户端的开发人员,那么最好使用第一个选项。
【讨论】:
以上是关于RESTful URL 设计:公共与私有 API、分层 API 设计模式、URI 与 URL 设计?的主要内容,如果未能解决你的问题,请参考以下文章