社交网络的 RESTful 设计

发布于 2025-01-06 10:47:52 字数 2512 浏览 1 评论 0 原文

我正在尝试理解 RESTful 设计,并且我想从社交网络的角度来理解它。我读过 REST API 设计规则手册,查看了其他一些书籍并阅读了很多在线资源。尽管如此,当将这些规则应用于现实世界的问题时,我意识到我并不完全理解一切。我想通过指定一些问题来了解我是否对 REST 有正确的理解:

层次结构与扁平化设计

让我们假设我使用

/users/42

现在标识了一个特定用户,该用户上传的照片最终会出现在

/users/42/photos

并且如果他/她标记了他的/她的朋友在照片中这些标签最终会出现在

/users/42/photos/1337/tags

但是这无法找到标记特定用户的所有照片。我应该为此想出一个不同的层次结构吗?似乎有点尴尬。我是否可以完全无视层次结构并结合这样的查询提供照片?

/photos?owner=42
/photos?tagged=42

当不同用户的内容不同时进行缓存

Web 服务应该设计为提供可缓存数据(以便客户端可以决定使用本地副本,如果它认为没有任何更改),但这如何影响不同用户的隐私设置用户?两个都登录的用户可能有权查看有关用户 42 的不同信息。这是否意味着我需要以某种方式为访问同一用户的个人资料信息的不同用户请求不同的 URI,或者缓存不会是一个只要用户提供不同的凭据就会出现问题吗?

从同一资源提供 HTML 和 JSON/XML

我提到的书指定了这样的规则:API 应该可以在以 api 开头的子域中访问,在他们的示例 http://api 中。足球.restapi.org。我计划对用户访问和机器访问(例如移动应用程序)使用相同的控制器。控制器将通过 Accept< 决定提供哪个视图(text/htmlapplication/jsonapplication/xml) HTTP 请求标头中的 /code> 字段。我认为出于某种原因这将是一个坏主意(因为用户希望看到子域 www,而不是 api),但我不明白为什么。 wwwapi 是否可以指向同一服务器,或者我真的应该尝试将 HTML 视图移动到不同的虚拟主机吗?为什么?

我相信 Ruby on Rails 将(约定优于配置)从同一个控制器提供 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 字段,但是有人认真考虑在他们所属的社交网络上切换语言吗?是否应该每次都将所有这些信息返回给调用者,还是我可以依赖这些设置?这可以与缓存一起使用吗?

I'm trying to wrap my head around RESTful design, and I'd like to understand it in terms of a social network. I've read REST API Design Rulebook, looked in some other books and read a lot of online resources. Still, when applying the rules to real-world problems I realize that I don't fully understand everything. I want to know if I have the correct understanding of REST by specifying some issues:

Hierarchy vs. flat design

Let's assume that I identify a particular user with

/users/42

Now, the photos uploaded by that user would end up in

/users/42/photos

and if he/she tags his/her friends in the photo those tags would end up in

/users/42/photos/1337/tags

But this provides no way of finding all photos where a particular user is tagged. Should I come up with a different hierarchy for that? It seems a bit awkward. Am I allowed to completely disregard the hierarchy and provide photos in combination with queries like this?

/photos?owner=42
/photos?tagged=42

Caching when content differs for different users

A Web Service should be designed to provide cacheable data (so that the client could decide to use the local copy, if it thinks that nothing has been changed), but how does that affect the privacy settings for different users? Two users, both logged in, might have the rights to view different information about e.g. user 42. Does that mean that I somehow need to request different URIs for different users accessing the profile information of the same user, or will the caching not be an issue as long as the users provide different credentials?

Providing both HTML and JSON/XML from the same resource

The book I mentioned specifies the rule that an API should be accessible at a subdomain starting with api, in their example http://api.soccer.restapi.org. I had planned to use the same controller for both user access and machine access (for example a mobile app). The controller would decide on which view to provide (text/html, application/json or application/xml) through the Accept field in the HTTP request header. I take it that that would be a bad idea for some reason (since a user would expect to see the subdomain www, not api), but I don't understand why. Can www and api point to the same server, or should I really try to move the HTML view to a different virtual host? Why?

I believe Ruby on Rails will (Convention over Configuration) provide both HTML and JSON from the same controller, thus sharing my idea that HTML and JSON are just different representations of the same data.

In other words, my book says that a certain resource should have one and only one URI, and that different representations should be provided based on the Accept field. Redirecting the user between different subdomain would violate the rule about providing any representation from the same resource, and duplicating the information (i.e. pointing two subdomains to the same virtual host) violates the rule about not providing multiple URIs for the same resource. Not providing the api subdomain violates yet another design rule. How can I solve this without violating any rule?

Limiting the data sent back

The query component should be used for pagination, but am I allowed to refuse to honor listing requests lacking search criteria and limit the number of items, without violating REST? I want to both reduce the database load and avoid having someone map the entire directory of users. I want

/users

to be an illegal request, while

/users?name=leet+hacker

would be valid but only return e.g. 100 items.

I also wonder if it's legal to return a subset of the database columns and more/all of them only if they're specifically requested using a query.

Controllers providing redundant data

I believe it's legal to provide a controller like

/users/me

but should it provide the exact same information as the document URI

/users/42

or should it redirect to it?

Extended rights for some users

Which is the RESTful way to provide additional functionality, for example administration rights? I'm now assuming that an administrator (of a photo, of a group of users or of the entire site) will be able to see more information about a particular object than other users. Should that information be kept at exactly the same URI and be sent automatically to the administrator but not to anyone else, should it be stored at a different location, should it be requested using a certain administrator query or provided in some other way?

Localization and settings updates

Although the majority of strings visible to the user should be provided by the view, there are some design decisions that might involve the API. The most obvious are names. Social networks sometimes allow the user to enter different names that are to be displayed in different language contexts. Names in some languages, like Russian and Arabic, are not easily transcribed automatically. In other languages, like Chinese, the local and international names can be completely different, with no resemblance or connection at all. What would be the RESTful way of handling this? I have a feeling that the answer will be the Accept-Language field, but is anyone seriously considering that for switching languages on the social network they belong to? Should all this information be returned to the caller every time, or can I rely on the settings? Will that work with a cache?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

回首观望 2025-01-13 10:47:52

正如@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 提供不同的内容,则该内容将不可缓存。您可以将站点分为两种类型的内容:公共内容和个性化内容。公共内容对于每个人来说应该是相同的并且可以被缓存。每个用户的个性化内容都不同,这意味着您可以缓存的内容量将大大减少(使用您在示例中使用的 URL 格式减少到零)。

要获得至少少量的个性化内容缓存,请按用户对内容进行分区,以便对于给定用户,您将获得一些缓存命中。例如,不要使用每个人都可以访问的 /users/42,而是使用 //users/42,其中 是请求用户的 userid。例如,用户 234 将使用 URI /234/users/42 访问用户 42 的个人资料页面。对于匿名用户,您可以删除 / 部分或为他们使用特定的用户 ID,例如 /public/users/42

从同一资源提供 HTML 和 JSON/XML

使用 Accept 标头。这就是它的用途。

限制发回的数据

您无需向 /users 提出非法请求。 Treat 是一个集合,并返回允许请求者查看的分页用户列表。例如,对于匿名请求,您可以提供一个空列表(或 204 无内容)

<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 hacker)会产生类似的内容

<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黑客可以被区分。

提供冗余数据的控制器

两者都是可以接受的。但是,如上所述(出于缓存原因)我将使用 //users/42,在这种情况下,您可能需要将 /42/users/me 重定向到<代码>/42/用户/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作为默认语言,但允许用户更改其设置中的语言。

As @Mark Dickinson mentioned, there are a lot of questions here, which really should be separate, but I'll do my best.

Hierarchy vs. flat design

There is nothing in REST suggesting you cannot have multiple parallel hierarchies (though I understand that doing so with Rails is awkward). Having /users/42/photos/owner and /photos/owner/42 containing the same set is fine. Similarly /users/42/photos/tagged and /photos/tagged/42 can contain the same set. However, you shouldn't be worried about the URIs at this time. There is an excellent article A RESTful Hypermedia API in Three Easy Steps that describes how to design your API. In this article, the URIs are decided as the last step.

Also, using the HATEOAS contstraint, these various URIs should be discovered at runtime by the client, through the links and forms provided by your application.

Caching when content differs for different users

If you are going to serve different content from the same URL, then it's not going to be cacheable. You can partition you site into two types of content, public and personalised. The public content should be the same for everyone and can be cached. The personalised content is different for each user, which means that the amount you can cache it will be dramatically reduced (reduced to zero using the URL format you have used in your examples).

To get at least a small amount of caching on personalised content, partition the content by the user, so that for a give user you'll get some cache hits. For example, instead of having /users/42 that everyone can access, use /<UID>/users/42 where is the userid of the requesting user. e.g. user 234 would access the profile page for user 42 using the URI /234/users/42. For anonymous users you could either remove the /<UID part or use a specific userid for them, such as /public/users/42.

Providing both HTML and JSON/XML from the same resource

Use the Accept header. That is what it is there for.

Limiting the data sent back

You don't need to make /users an illegal request. Treat is a a collection and return a paginated list of users that the requester is permitted to see. For example for an anonymous request you might provide an empty list (or 204 No Content)

<users/>

and for a particular logged in user, you might provide their friends.

<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>

when that user then GETs /users?page=2 you would then provide the next page of results

<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>

With the last page of results providing no next link. To add a searching capability, you just add an appropriate form as part of the response.

<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>

The results of the search would be paginated, just like the /users list. e.g., searching for leet hacker (Assuming you have permission to view a lot of leet hackers in the system) would produce something like

<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>

however you will probably need to provide more details in the user element, so the leet hackers can be differentiated.

Controllers providing redundant data

Both are acceptable. However as above (for caching reasons) I would use /<UID>/users/42, in which case you might want to redirect /42/users/me to /42/users/42.

This also helps from an analytics point of view as you might want to separately track a users access the their own page, from users access to other users page, to find out which users are popular and which are narcissistic. You may even find a correlation between the two :)

Extended rights for some users

Provide the admin links and forms in the appropriate response. For instance, accessing details of someone else's image might provide

<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>

but for the image owner, it might provide

<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>

Localization and settings updates

Use Accept-Language for the default language, but allow a user to change the language in their settings.

习惯那些不曾习惯的习惯 2025-01-13 10:47:52

如果您正在使用 WCF Web Api(或者即使您没有),那么查看其中的 OData 支持可能是值得的。它确实解决了一些使事物更易于搜索的问题。

If you're using WCF Web Api (or even if you aren't) it might be worth your while looking at the OData support in there. It does address some of the issues of making things more searchable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文