我正在研究REST API
,我正在努力了解如何处理分层资源。
让我们从一个简单的例子开始。在我的API中,我有用户,用户个人资料和评论。
用户的资源表示应为:
User: {
"u1": "u1value", // User's attributes
"u2": "u2value",
...
"links": [{
"rel": "profile",
"href": "http://..." // URI of the profile resource
}, {
"rel": "review",
"href": "http://..." // URI of the review resource
}]
}
用户个人资料资源表示应为:
UserProfile: {
"p1": "p1value", // Profile attributes
"p2": "p2value",
...
"links": [{
"rel": "owner",
"href": "http://..." // URI of the user resource
}]
}
审核资源表示应该是:
Review: {
"r1": "r1value", // Review attributes
"r2": "r2value",
...
"links": [{
"rel": "owner",
"href": "http://..." // URI of the user resource
}]
}
资源URI可以是:
http://api.example.com/users/{userid}
:访问用户资源http://api.example.com/users/{userid}/profile
:访问用户的个人资料资源http://api.example.com/users/{userid}/review
:访问用户的评论资源现在我想创建一个新用户:
POST http://api.example.com/users {"u1": "bar", "u2": "foo"}
我收回了新的userid = 42 POST http://api.example.com/users/42/profile {"p1": "baz", "p2": "asd"}
PUT http://api.example.com/users {"u1": "bar", "u2": "foo", links: [{"rel": "profile", "href": "http://api.example.com/users/42/profile"]}
我的担忧:
答案 0 :(得分:16)
您的问题得到妥善解决,问题清单正确无误。如果我建议,你的方法看起来非常像你正在使用关系数据库方法并且正在执行INSERT,从用于下一个INSERT的序列中检索PK,依此类推。
让服务器保持参照完整性
作为观察,即使遵循原始方案,完全省略步骤3 。检索用户文档时可见的links
中的URI应由服务器根据配置文件记录的存在生成。
例如,如果使用关系后端,则从USERS中选择SELECT以获取用户记录。接下来,从PROFILES中选择。如果有记录,则修改返回数据结构以包含引用。
发布整个文档
解决您提出的其他问题的常用方法是允许将整个文档发布到用户URL(如MongoDB等NoSQL数据库)。这里的文档是用户和个人资料:
{
"u1": "bar",
"u2": "foo",
"profile": {
"p1": "baz",
"p2": "asd"
}
}
在这种方法中,服务器上的端点接收嵌套结构(文档)并执行INSERT到USERS,检索PK,然后使用此PK执行INSERT到PROFILES。在服务器端执行此操作可以解决几个问题:
请注意,除了上面详述的之外,您使用此方法 - 您仍然希望能够直接访问用户的个人资料。
GET - 客户可以指定字段
与来自知名公司的API进行比较很有意思。以LinkedIn为例。在developer API中,用户的默认GET只返回用户的姓名,标题和URI。
但是,如果请求指定了其他字段,则可以获取嵌套数据,例如http://developer.linkedin.com/documents/understanding-field-selectors中的第二个示例返回用户的名称以及他们所持位置的公司名称列表。您可以为“个人档案和评论”实施类似的方案。
用于更新文档属性的PATCH
通过插入和查询,可能值得考虑如何更新(PATCH)数据。覆盖一个字段是显而易见的,所以你可以,例如PATCH到http://api.example.com/users/42以下内容:
{
"u1": null,
"u2": "new-foo",
"profile": { "p1": "new-baz"}
}
将取消设置u1
,将u2
设置为new-foo
并将配置文件的p1
更新为new-baz
。请注意,如果缺少某个字段(p2
),则不会修改该字段。如this answer中所述,PATCH优于较旧的PUT。
如果您只需要更新个人资料,请将新的个人资料记录直接修改为http://api.example.com/users/42/profile
DELETE应该级联
最后,可以使用指向要删除的资源的DELETE方法进行删除 - 无论是用户,个人资料还是审阅。实施级联删除,以便删除用户删除他/她的个人资料和评论。
答案 1 :(得分:2)
您应该坚持HATEOAS,并取消引用您在回复中获得的网址:
为方便访问,我们可以说User.profile
包含href
link
和rel == profile
。
使用您描述的POST ...但它不应该返回一个id,而是一个用户,并且不会返回它的链接。
User: {
"u1": "bar", // User's attributes
"u2": "foo",
...
"profile": "http://api.example.com/users/42/profile",
"links": [{
"rel": "profile",
"href": "http://api.example.com/users/42/profile"
},
...
]
}
此时,User.profile中的个人资料资源(可能是http://api.example.com/users/42/profile
,或者您将来迁移到的任何位置)都是默认配置文件应该是什么,例如:一个空文档或仅填充所有者链接。
profile = GET User.profile
profile.p1 = "baz"
profile.p2 = "asd"
PUT profile to the same url you just dereferenced
通过取消引用文档上的hrefs而不是构建具有id的URL,您可以从响应中获取,您的客户端不必在API更改时进行更改。喜欢的时候 - 个人资料已移至http://profiles.cdn.example.com/ - 个人资料获得p3值
“旧”API客户端将继续工作,而无需更改任何代码。
答案 2 :(得分:1)
实际上,从成功的步骤(1)开始,您应该获得HTTP代码201 Created和新创建的资源的地址(URL),而不仅仅是ID号。如果步骤(2)失败,则REST API应指示问题是否与客户端有关,例如格式错误的文档(问题代码4xx)或服务器(5xx)。例如,如果在此期间资源42被删除,则应返回代码404 Not Found。
无状态REST API存在问题 - 它们不支持由多个请求组成的事务。为此,您必须在服务器上维护会话(状态)。
顺便说一下,示例中步骤(3)中的URL表示您要替换所有用户,可能应该阅读http://api.example.com/users/42。
您可以选择一次提交完整的用户+个人资料文档,在一个原子事务中拆分为两个数据库记录,或允许部分用户数据的持久性,即没有个人资料的用户。
选择取决于上下文。例如,用户没有配置文件(因此可以由用户提供)可能非常精细。相反,拥有不属于任何用户的个人资料记录可能是不可接受的。关于强制执行此逻辑的讨论超出了您的问题的范围,并且将根据您选择的持久性存储(数据库)的类型而有所不同。关系数据库使用外键强制执行此操作。
答案 3 :(得分:0)
我相信你的电话应该是这样的
1)创建用户
POST http://api.example.com/users + params in payload
如果它返回HTTP 201 +用户ID,那么您可以继续创建配置文件。否则,您可以按照自己的方式处理异常。在开始创建配置文件之前,您应该等待第一个电话回来。
2)创建与用户42相关联的配置文件(如果用户创建正常)
POST http://api.example.com/users/42/profile + params in payload
返回HTTP 201 +个人资料ID
您的后端将负责更新您的用户对象和配置文件对象(以及您的数据库),以便用户42将链接到新配置文件。如果后端无法链接对象,您可以发回500错误来解释发生的事情。
所以我认为我会跳过第3步。
现在我明白你的观点是用户必须有个人资料
我看到2个解决方案
1)您可以在创建用户时创建空的关联配置文件。然后通过查询用户,您可以获取配置文件ID并使用PUT进行修改(我真的不喜欢这个解决方案,因为当您要求api创建用户时,它应该只创建一个用户而不是其他任何东西,但实际上是配置文件是强制性的,它不像看起来那么丑陋。)
2)您可以在您的用户中拥有一个属性,说明用户是否正确(意味着他有相关的个人资料)。在创建过程结束时,如果您的用户42不正确,您可以删除它或重试配置文件创建...然后您只能使用/ users查询正确的用户?isCorrect = true
3)让客户端将用户视为无个人资料的事实 - >显示一个弹出窗口,要求创建个人资料......
请查看此document以获取REST API最佳做法
也许您还可以查看试图处理对象之间关系的HAL。
最后但并非最不重要的是,您可以关注api-craft google group您可能会在那里找到与您的问题相关的有趣主题。