RESTful URL设计:公共API vs 私有API,层次结构API设计模式,URI vs URL设计?

4
我经常遇到类似这样的问题,与此非常相似: RESTful URL设计中的层次结构。 假设该服务只允许用户上传文档。
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}/...或/documents/{id}来实现。这类似于如何组织Unix文件(虽然/users/...也是另一种组织文件的方式...)。你看,还有一种URI与URL设计的哲学。
另一个考虑因素是API是否对用户可见。如果这只是一个后端API,只有开发者可以访问(后端<-前端服务器<-前端AJAX),那么一个网站用户可能会更喜欢/users/{name}/documents/{id/name},而一些程序员则会因为API是公共的(如Twitter API)而不喜欢这个长URL。
大家对这些问题有什么看法呢?
3个回答

2

从实际角度来看,以上所有内容都是正确的。

但从哲学上来讲,我认为这归结于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时应该很明显,并且不应该被本地或技术特定的约定所拖累。

1

好的,所以你无疑遇到过一些惯例,进行了研究或偶然发现。但是,虽然我可能不是100%正确,但现在先放下惯例。

在编写RESTful服务时,首先考虑流程的层次结构和谁拥有什么。换句话说,如果我想删除属于一组人的文档,我不会编写像/user/doc/{id}这样的url。类似/user/doc/{id}通常意味着您首先处理与用户相关的内容,然后是文档,最后是特定于该用户的文档。

所以我想说的是,我通常尝试将GET、POST、PUT、DELETE方法保留给实体,并将与实体层次结构相关的URL。它是否百分之百有效,不是,但对于任何跟随我的程序员来说,它是否很明显,几乎总是肯定的。

你提到的/documents和/user/docs的例子,对我来说意味着你有两个不同的文档。一个是用户生命周期之外的文档,另一个是与用户账户相关联的文档,它们会随着用户的存在而存在或者消失。如果在我的例子中你使用/documents/usr/{id},那么即使你使用/documents/usr/{id}删除了该文档,我认为你只是删除了该用户对文档的引用,因为文档在层次结构中高于用户。同样地,如果你使用/user/document/{id},我认为该文档是特定于用户的,并且将随该用户一起消失。希望这样解释清楚了。再次强调,我并不是说我100%正确,总会有特殊情况。

1
我在这里没有看到任何问题。REST没有URI的限制,那是一个实现细节,完全取决于服务开发人员。
REST是关于M2M通信而不是H2M通信,所以最终用户不会看到任何有关REST URIs的内容。
如果应用了HATEOAS约束,客户端也不需要关心URIs。如果您不喜欢超媒体响应,而是更喜欢文档化URI模板,则我认为较短的URIs更好,因为客户端开发人员更容易输错它们。
离题:
我目前正在阅读Vernon的书中关于DDD聚合的内容,这里有一个有趣的相似之处。如果要访问用户文档,则有两种建模选项。
第一种选择是将文档放在用户聚合后面,因此必须使用以下方式才能通过用户聚合访问它们:
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},
    {},
    {},
    ...
]

很难找到任何基于用户属性和用户文档的验证规则,因此我可能会选择第二个选项。

当然,也有例外情况,例如如果第二个选项速度过慢或者您想要简化添加用户文档到客户端开发人员,则最好使用第一个选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接