Ric*_*ker 276 rest entity-relationship
想象一下,你有2个实体,玩家和团队,玩家可以在多个团队中.在我的数据模型中,我有一个每个实体的表,以及一个用于维护关系的连接表.Hibernate可以很好地处理这个问题,但是我如何在RESTful API中公开这种关系呢?
我可以想几个方面.首先,我可能让每个实体都包含另一个实体的列表,因此Player对象将拥有它所属的Teams列表,并且每个Team对象都有一个属于它的Players列表.因此,要向团队添加播放器,您只需将播放器的表示形式发布到端点,例如POST /player或POST /team,并将相应的对象作为请求的有效负载.这对我来说似乎是最"RESTful"但感觉有点奇怪.
/api/team/0:
{
name: 'Boston Celtics',
logo: '/img/Celtics.png',
players: [
'/api/player/20',
'/api/player/5',
'/api/player/34'
]
}
/api/player/20:
{
pk: 20,
name: 'Ray Allen',
birth: '1975-07-20T02:00:00Z',
team: '/api/team/0'
}
Run Code Online (Sandbox Code Playgroud)
我能想到的另一种方法就是将关系本身作为一种资源来揭露.因此,要查看给定团队中所有玩家的列表,您可以执行GET /playerteam/team/{id}或类似的操作并获取PlayerTeam实体列表.要将球员添加到球队,请/playerteam使用适当构建的PlayerTeam实体作为有效负载进行POST .
/api/team/0:
{
name: 'Boston Celtics',
logo: '/img/Celtics.png'
}
/api/player/20:
{
pk: 20,
name: 'Ray Allen',
birth: '1975-07-20T02:00:00Z',
team: '/api/team/0'
}
/api/player/team/0/:
[
'/api/player/20',
'/api/player/5',
'/api/player/34'
]
Run Code Online (Sandbox Code Playgroud)
这是什么最好的做法?
fum*_*chu 250
制作一组单独的/memberships/资源.
/teams/3/players/该列表将失效,但您不希望备用URL /players/5/teams/保持缓存状态.是的,不同的缓存将包含不同年龄的每个列表的副本,并且我们无法做到这一点,但我们至少可以通过限制我们需要使实体无效的实体数量来最小化用户POST更新的混淆在他们的客户端的本地缓存中唯一一个在/memberships/98745(见"交替指数"的埃兰的讨论生命超越分布式事务的更详细的讨论)./players/5/teams或/teams/3/players(但不是两者)来实现上述2点.让我们假设前者.但是,在某些时候,您需要保留当前成员资格/players/5/teams/的列表,并且能够在某处引用过去的成员资格.让超链接到列表中的资源,然后你可以添加当你喜欢,而不必打破大家的书签为个人会员资源.这是一般概念; 我相信你可以想象其他类似的期货更适用于你的具体情况./players/5/memberships//memberships/{id}//players/5/past_memberships/Don*_*ows 118
在RESTful接口中,您可以通过将这些关系编码为链接来返回描述资源之间关系的文档.因此,可以说一个团队有一个文档资源(/team/{id}/players),它是/player/{id}团队中玩家()的链接列表,玩家可以拥有一个文档资源(/player/{id}/teams),该资源是一个链接列表,该团队是玩家所在团队的链接.的成员.不错,对称.你可以轻松地在该列表上进行地图操作,甚至可以给自己的关系提供一个关系(可以说他们有两个ID,这取决于你是在考虑团队优先关系还是玩家优先关系)如果这样可以让事情变得更容易.唯一棘手的一点是,如果从一端删除它,你必须记住从另一端删除关系,但是通过使用底层数据模型严格处理它,然后让REST接口成为该模型将使这更容易.
关系ID可能应该基于UUID或同样长且随机的东西,无论您为团队和玩家使用何种类型的ID.这将允许您为关系的每一端使用相同的UUID作为ID组件,而不必担心冲突(小整数没有这种优势).如果这些成员关系具有除了与玩家和团队双向关联的事实以外的任何属性,他们应该拥有独立于玩家和团队的自己的身份; /player/{playerID}/teams/{teamID}然后,玩家»团队视图()上的GET 可以进行HTTP重定向到双向视图(/memberships/{uuid}).
我建议使用XLink xlink:href属性在您返回的任何XML文档中编写链接(如果您恰好正在生成XML).
man*_*ana 60
我会将这种关系映射到子资源,一般设计/遍历将是:
# team resource
/teams/{teamId}
# players resource
/players/{playerId}
# teams/players subresource
/teams/{teamId}/players/{playerId}
在Restful-terms中,它有很多不用考虑SQL和连接,而是更多地考虑集合,子集合和遍历.
一些例子:
# getting player 3 who is on team 1
# or simply checking whether player 3 is on that team (200 vs. 404)
GET /teams/1/players/3
# getting player 3 who is also on team 3
GET /teams/3/players/3
# adding player 3 also to team 2
PUT /teams/2/players/3
# getting all teams of player 3
GET /players/3/teams
# withdraw player 3 from team 1 (appeared drunk before match)
DELETE /teams/1/players/3
# team 1 found a replacement, who is not registered in league yet
POST /players
# from payload you get back the id, now place it officially to team 1
PUT /teams/1/players/44
如你所见,我没有使用POST将球员放到球队,而是PUT,它可以更好地处理球员和球队的n:n关系.
Set*_*eth 19
现有的答案并没有解释一致性和幂等性的作用 - 这会激发他们UUIDs对ID的随机数的推荐,而PUT不是POST.
如果我们考虑的情况是我们有一个简单的场景,如" 向团队添加新玩家 ",我们会遇到一致性问题.
因为玩家不存在,我们需要:
POST /players { "Name": "Murray" } //=> 302 /players/5
POST /teams/1/players/5
Run Code Online (Sandbox Code Playgroud)
但是,如果客户端操作在POSTto 之后失败/players,我们创建了一个不属于团队的玩家:
POST /players { "Name": "Murray" } //=> 302 /players/5
// *client failure*
// *client retries naively*
POST /players { "Name": "Murray" } //=> 302 /players/6
POST /teams/1/players/6
Run Code Online (Sandbox Code Playgroud)
现在我们有一个孤立的重复玩家/players/5.
为了解决这个问题,我们可能会编写自定义恢复代码来检查与某些自然密钥匹配的孤立播放器(例如Name).这是需要测试的自定义代码,需要花费更多的金钱和时间等
为了避免需要自定义恢复代码,我们可以实现PUT而不是POST.
来自RFC:
意图
PUT是幂等的
对于幂等的操作,它需要排除外部数据,例如服务器生成的id序列.这就是为什么人们都推荐PUT和UUID对于s Id小号一起.
这使我们能够重新运行/players PUT和/memberships PUT没有后果:
PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK
// *client failure*
// *client YOLOs*
PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK
PUT /teams/1/players/23lkrjrqwlej
Run Code Online (Sandbox Code Playgroud)
一切都很好,除了重试部分失败之外,我们不需要做任何其他事情.
这更像是对现有答案的补充,但我希望它能让我们了解ReST的灵活性和可靠性.
我首选的方案是创建三个资源:Players,Teams和TeamsPlayers。
因此,要获得团队中的所有球员,只需访问Teams资源并致电即可获得其所有球员GET /Teams/{teamId}/Players。
另一方面,要获得玩家参与的所有团队,请在中获取Teams资源Players。致电GET /Players/{playerId}/Teams。
并且,要获取多对多关系呼叫GET /Players/{playerId}/TeamsPlayers或GET /Teams/{teamId}/TeamsPlayers。
请注意,在此解决方案中,当您调用时GET /Players/{playerId}/Teams,您将获得一个Teams资源数组,这与您在调用时获得的资源完全相同GET /Teams/{teamId}。反向遵循相同的原理,Players当调用时,您将获得一组资源GET /Teams/{teamId}/Players。
在两个调用中,都不返回有关该关系的信息。例如,不contractStartDate返回no ,因为返回的资源没有有关该关系的信息,只有它自己的资源。
要处理nn关系,请调用GET /Players/{playerId}/TeamsPlayers或GET /Teams/{teamId}/TeamsPlayers。这些调用返回确切的资源TeamsPlayers。
该TeamsPlayers资源已id,playerId,teamId属性,以及一些其他人描述这种关系。而且,它具有处理它们的必要方法。GET,POST,PUT,DELETE等将返回,包含,更新,删除关系资源。
该TeamsPlayers资源实现一些查询,例如GET /TeamsPlayers?player={playerId}返回TeamsPlayers玩家所标识的所有关系{playerId}。遵循相同的想法,使用GET /TeamsPlayers?team={teamId}来返回团队中所有TeamsPlayers已参加的{teamId}比赛。在任一GET调用中,资源都TeamsPlayers被返回。返回与该关系有关的所有数据。
在致电GET /Players/{playerId}/Teams(或GET /Teams/{teamId}/Players)时,资源Players(或Teams)调用TeamsPlayers以使用查询过滤器返回相关的球队(或球员)。
GET /Players/{playerId}/Teams 像这样工作:
- 找到所有TeamsPlayers的球员有ID = playerId。(
GET /TeamsPlayers?player={playerId})- 循环返回的TeamsPlayers
- 使用从TeamsPlayers获得的teamId,调用
GET /Teams/{teamId}并存储返回的数据- 循环结束后。返回循环中的所有团队。
您可以在调用时使用相同的算法从团队中获取所有球员GET /Teams/{teamId}/Players,但交换团队和球员。
我的资源如下所示:
/api/Teams/1:
{
id: 1
name: 'Vasco da Gama',
logo: '/img/Vascao.png',
}
/api/Players/10:
{
id: 10,
name: 'Roberto Dinamite',
birth: '1954-04-13T00:00:00Z',
}
/api/TeamsPlayers/100
{
id: 100,
playerId: 10,
teamId: 1,
contractStartDate: '1971-11-25T00:00:00Z',
}
Run Code Online (Sandbox Code Playgroud)
此解决方案仅依赖REST资源。尽管可能需要一些额外的调用才能从玩家,团队或其关系中获取数据,但是所有HTTP方法都易于实现。POST,PUT,DELETE非常简单明了。
每当创建,更新或删除关系时Players,Teams资源都会自动更新。
| 归档时间: |
|
| 查看次数: |
81687 次 |
| 最近记录: |