我试图围绕RESTful设计,并且我想在社交网络方面理解它。我已阅读REST API Design Rulebook,查看了其他一些书籍并阅读了大量在线资源。然而,当将规则应用于现实世界的问题时,我意识到我并不完全理解一切。我想通过指定一些问题来了解我是否对REST有正确的理解:
我们假设我使用
识别特定用户/users/42
现在,该用户上传的照片最终会显示在
中/users/42/photos
如果他/她在照片中标记了他/她的朋友那些标签最终将会出现在
中/users/42/photos/1337/tags
但这无法找到特定用户被标记的所有照片。我应该为此提出不同的层次结构吗?这看起来有点尴尬。我是否可以完全忽略层次结构,并提供与此类查询相结合的照片?
/photos?owner=42
/photos?tagged=42
Web服务应设计为提供可缓存的数据(以便客户端可以决定使用本地副本,如果它认为没有任何更改),但这会如何影响不同用户的隐私设置?两个用户都登录,可能有权查看有关例如用户42.这是否意味着我需要为访问同一用户的配置文件信息的不同用户请求不同的URI,或者只要用户提供不同的凭据,缓存就不会成为问题?
我提到的这本书指出了API应该在api
开头的子域中可访问的规则,在他们的示例http://api.soccer.restapi.org
中。我曾计划为用户访问和机器访问使用相同的控制器(例如移动应用程序)。控制器将通过HTTP请求标头中的text/html
字段决定提供哪个视图(application/json
,application/xml
或Accept
)。我认为,由于某些原因,这会是一个坏主意(因为用户希望看到子域www
,而不是api
),但我不明白为什么。 www
和api
可以指向同一台服务器,还是应该尝试将HTML视图移动到其他虚拟主机?为什么呢?
我相信Ruby on Rails(Convention over Configuration)会从同一个控制器提供HTML和JSON,因此我认为HTML和JSON只是同一数据的不同表示。
换句话说,我的书说某个资源应该只有一个URI,并且应该根据Accept
字段提供不同的表示。在不同子域之间重定向用户将违反关于从同一资源提供任何表示的规则,并且复制信息(即,将两个子域指向同一虚拟主机)违反了关于不为同一资源提供多个URI的规则。不提供api
子域违反了另一个设计规则。如何在不违反任何规则的情况下解决这个问题?
查询组件应该用于分页,但是我是否可以拒绝遵守缺少搜索条件的列表请求并限制项目数量而不违反REST?我想减少数据库负载,避免让某人映射整个用户目录。我想要
/users
是非法请求,而
/users?name=leet+hacker
有效但只返回例如100件。
我还想知道,只有在使用查询专门请求的情况下,才能返回数据库列的子集以及更多/全部数据列。
我认为提供像
这样的控制器是合法的/users/me
但是它应该提供与文档URI完全相同的信息
/users/42
还是应该重定向到它?
哪种RESTful方式可以提供其他功能,例如管理权限?我现在假设管理员(照片,一组用户或整个网站的管理员)将能够看到有关特定对象的更多信息,而不是其他用户。该信息应该保存在完全相同的URI中并自动发送给管理员而不是其他任何人,如果它存储在不同的位置,是应该使用某个管理员查询请求还是以其他方式提供?
尽管视图应提供用户可见的大多数字符串,但仍有一些可能涉及API的设计决策。最明显的是名字。社交网络有时允许用户输入要在不同语言环境中显示的不同名称。某些语言的名称(如俄语和阿拉伯语)不易自动转录。在其他语言中,如中文,本地和国际名称可能完全不同,完全没有相似之处或联系。什么是RESTful处理方式?我有一种感觉,答案将是Accept-Language
字段,但有人认真考虑在他们所属的社交网络上切换语言吗?是否应该每次都将所有这些信息返回给呼叫者,还是可以依赖设置?这可以用缓存吗?
答案 0 :(得分:13)
正如@Mark Dickinson所提到的,这里有很多问题,这些问题确实应该是分开的,但我会尽我所能。
层次结构与平面设计
REST中没有任何内容表明您不能拥有多个并行层次结构(尽管我知道使用Rails这样做很尴尬)。让/users/42/photos/owner
和/photos/owner/42
包含相同的集合就可以了。同样,/users/42/photos/tagged
和/photos/tagged/42
可以包含相同的集合。但是,您现在不应该担心URI。有一篇很好的文章A RESTful Hypermedia API in Three Easy Steps介绍了如何设计API。在本文中,URI被确定为最后一步。
此外,使用HATEOAS约束,客户端应通过应用程序提供的链接和表单在运行时发现这些不同的URI。
当内容因不同用户而异时进行缓存
如果您要从同一个网址提供不同的内容,那么它将无法缓存。您可以将站点划分为两种类型的内容,即公共内容和个性化内容。每个人的公共内容应该相同,并且可以缓存。每个用户的个性化内容都不同,这意味着您可以缓存的数量将大大减少(使用您在示例中使用的URL格式减少到零)。
要在个性化内容上至少进行少量缓存,请由用户对内容进行分区,这样对于给予用户,您将获得一些缓存命中。例如,不要让每个人都可以访问/users/42
,而是使用/<UID>/users/42
,其中是请求用户的用户ID。例如用户234将使用URI /234/users/42
访问用户42的简档页面。对于匿名用户,您可以删除/<UID
部分或为其使用特定的用户ID,例如/public/users/42
。
从同一资源提供HTML和JSON / XML
使用Accept
标题。这就是它的用途。
限制发回的数据
您无需将/users
作为非法请求。 Treat是一个集合,返回允许请求者查看的分页用户列表。例如,对于匿名请求,您可以提供一个空列表(或204 No Content)
<users/>
对于特定登录用户,您可以提供他们的朋友。
<users>
<user id="42" href="/users/42" name="John Doe"/>
<user id="53" href="/users/53" name="Jane Doe"/>
...
<next href="/users?page=2"/>
</users>
当该用户然后获取/users?page=2
时,您将提供下一页结果
<users>
<user id="69" href="/users/69" name="John Smo"/>
<user id="84" href="/users/84" name="Jane Smo"/>
...
<next href="/users?page=3"/>
<prev href="/users"/>
</users>
结果的最后一页不提供next
链接。要添加搜索功能,只需添加适当的表单作为响应的一部分。
<users>
<user id="69" href="/users/69" name="John Smo"/>
<user id="84" href="/users/84" name="Jane Smo"/>
...
<next href="/users?page=2"/>
<prev href="/users"/>
<search href="/users" method="get">
<name cardinality="required" type="regex"/>
</search>
</users>
搜索结果将被分页,就像/users
列表一样。例如,搜索leet hacker
(假设您有权查看系统中的许多leet黑客)会产生类似
<users>
<user id="234" href="/users/234" name="leet hacker"/>
<user id="999" href="/users/999" name="leet hacker"/>
...
<next href="/users?name=leet+hacker&page=2"/>
<search href="/users" method="get">
<name cardinality="required" type="regex"/>
</search>
</users>
但是你可能需要在用户元素中提供更多细节,因此可以区分leet黑客。
提供冗余数据的控制器
两者都可以接受。但是,如上所述(出于缓存原因),我会使用/<UID>/users/42
,在这种情况下,您可能希望将/42/users/me
重定向到/42/users/42
。
这也有助于从分析的角度来看,因为您可能希望单独跟踪用户访问他们自己的页面,从用户访问其他用户页面,找出哪些用户是受欢迎的以及哪些用户是自恋的。你甚至可以找到两者之间的相关性:)
某些用户的扩展权限
在相应的回复中提供管理员链接和表单。例如,访问其他人的图像的详细信息可能会提供
<image id="266" href="/photos/266" caption="leet hacker with computer"
src="http://us.123rf.com/400wm/400/400/creatista/creatista0911/creatista091100003/5827629.jpg"/>
<tagged-users>
<user id="234" href="/users/234" name="leet hacker"/>
</tagged-users>
<owner id="234" href="/users/234" name="leet hacker"/>
</image>
但是对于图片所有者,它可能会提供
<image id="266" href="/photos/266" caption="leet hacker with computer"
src="http://us.123rf.com/400wm/400/400/creatista/creatista0911/creatista091100003/5827629.jpg"/>
<tagged-users>
<tagged-user id="234" href="/users/234" name="leet hacker">
<delete href="/photos/266/tagged/234" method="delete"/>
</tagged-user>
<add href="/photos/266" method="put">
<user cardinality="required" type="user-id"/>
</add>
</tagged-users>
<owner id="234" href="/users/234" name="leet hacker"/>
<delete href="/photos/266" method="delete"/>
<update href="/photos/266" method="put">
<caption cardinality="optional" type="string"/>
</update>
</image>
本地化和设置更新
使用Accept-Language
作为默认语言,但允许用户更改其设置中的语言。
答案 1 :(得分:0)
如果您正在使用WCF Web Api(或者即使您不是),那么在那里查看OData支持可能是值得的。它确实解决了使事情更易于搜索的一些问题。