微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在RESTful API中处理多对多关系?

如何解决如何在RESTful API中处理多对多关系?

| 假设您有2个实体,玩家和团队,其中玩家可以在多个团队中。在我的数据模型中,我为每个实体都有一个表,以及一个用于维护关系的联接表。 Hibernate可以很好地处理此问题,但是如何在RESTful API中公开这种关系? 我可以想到几种方法。首先,我可能让每个实体都包含另一个实体的列表,所以一个Player对象将具有一个它所属的团队列表,而每个Team对象将具有一个属于它的Player列表。因此,要将一个玩家添加到团队中,您只需将玩家的表示发布到端点,例如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\'
}
我可以想到的另一种方式是将关系本身作为一种资源公开。因此,要查看给定团队中所有球员的列表,您可以执行GET
/playerteam/team/{id}
或类似的方法,然后获取PlayerTeam实体的列表。要将玩家添加到团队中,请使用适当构建的PlayerTeam实体作为有效载荷的POST
/playerteam
/api/team/0:

{
    name: \'Boston Celtics\',logo: \'/img/Celtics.png\'
}

/api/player/20:

{
    pk: 20,team: \'/api/team/0\'
}

/api/player/team/0/:

[
    \'/api/player/20\',\'/api/player/34\'        
]
最佳做法是什么?     

解决方法

        在RESTful界面中,您可以通过将资源之间的关系编码为链接来返回描述资源之间关系的文档。因此,可以说一个团队拥有一个文档资源(
/team/{id}/players
),该文档资源是指向团队中玩家的链接(
/player/{id}
),而一个玩家可以具有文档资源(
/player/{id}/teams
),该文档资源是指向该团队的链接的清单玩家是其中的一员。尼斯和对称。您可以轻松轻松地对该列表上的地图操作进行操作,甚至可以为关系指定一个自己的ID(可以说,他们有两个ID,具体取决于您考虑的是团队优先还是玩家优先)。事情变得容易。唯一棘手的一点是,如果您从一端删除了该关系,则还必须记住要从另一端删除该关系,但是要通过使用基础数据模型然后使用REST接口作为视图来进行严格处理该模型的使用将使其变得更容易。 关系ID可能应该基于UUID或同样长且随机的某种值,而不管您用于团队和球员的ID的类型如何。这样一来,您就可以为关系的每一端使用与ID组件相同的UUID,而不必担心冲突(小整数没有这种优势)。如果这些成员关系具有除以双向方式关联球员和团队的事实以外的任何其他属性,则它们应具有独立于球员和团队的身份;然后,玩家»团队视图(
/player/{playerID}/teams/{teamID}
)上的GET可以执行HTTP重定向到双向视图(
/memberships/{uuid}
)。 我建议使用XLink
xlink:href
属性在返回的任何XML文档(如果您碰巧正在生成XML)中编写链接。     ,单独制作ѭ12资源。 REST就是要开发可演化的系统。目前,您可能只关心给定的球员在给定的团队中,但是在将来的某个时候,您将希望使用更多数据来注释这种关系:他们在该团队中待了多长时间,谁提到了他们到那个团队,他们的教练在那个团队里,等等。 REST依靠缓存来提高效率,这需要考虑缓存的原子性和无效性。如果您将新实体发布到a13ѭ,则该列表将无效,但您不希望备用URL
/players/5/teams/
保持缓存。是的,不同的缓存将具有不同年龄的每个列表的副本,对此我们无能为力,但是我们至少可以通过限制实体数量来最大程度地减少用户对更新进行发布的困惑我们需要在15英镑时将其客户的本地缓存无效,并且只对一个无效(请参阅Helland对“分布式交易之外的生活”中“替代指数”的讨论,以进行更详细的讨论)。 您只需选择
/players/5/teams
/teams/3/players
(但不能同时选择两者)即可实现以上2点。让我们假设前者。但是,在某个时候,您将需要为当前成员资格列表保留
/players/5/teams/
,但仍能够在某处引用过去的成员资格。将
/players/5/memberships/
链接到
/memberships/{id}/
资源的超链接列表,然后您可以在需要时添加ѭ21,,而不必破坏每个会员资源的书签。这是一个一般概念;我相信您可以想象其他类似的期货更适合您的特定情况。     ,        我将这种关系与子资源映射,那么一般的设计/遍历将是:
# team resource
/teams/{teamId}

# players resource
/players/{playerId}

# teams/players subresource
/teams/{teamId}/players/{playerId}
在Restful术语中,它在不考虑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关系。     ,        现有的答案并不能解释一致性和幂等的作用-促使他们建议ID为
UUIDs
/随机数,而建议为
PUT
而不是ѭ26。 如果我们考虑一个简单的场景,例如“向团队添加新球员”,则会遇到一致性问题。 由于播放器不存在,因此我们需要:
POST /players { \"Name\": \"Murray\" } //=> 302 /players/5
POST /teams/1/players/5
但是,如果在operation26ѭ到
/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
现在我们在
/players/5
有一个孤立的重复玩家。 为了解决这个问题,我们可能会编写自定义恢复代码,以检查与某些自然键(例如
Name
)匹配的孤立玩家。这是需要测试的自定义代码,花费更多的金钱和时间等 为了避免需要自定义恢复代码,我们可以实现
PUT
而不是
POST
。 从RFC:   ѭ25的意图是幂等的 为了使操作成为幂等,它需要排除外部数据,例如服务器生成的ID序列。这就是为什么人们同时推荐
PUT
UUID
来搭配ѭ38的原因。 这使我们可以重新运行
/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
一切都很好,我们只需要重试部分故障即可。 这更多地是现有答案的附录,但我希望它能将ReST的灵活性和可靠性看得更清楚。     ,我的首选解决方案是创建三个资源:
Players
Teams
TeamsPlayers
。 因此,要获得团队中的所有球员,只需进入
Teams
资源,然后致电
GET /Teams/{teamId}/Players
获得所有球员。 另一方面,要获得一名玩家参与的所有团队,请在the44ѭ之内获得
Teams
资源。致电ѭ51。 并且,要获得多对多关系,请致电
GET /Players/{playerId}/TeamsPlayers
GET /Teams/{teamId}/TeamsPlayers
。 请注意,在此解决方案中,当您调用
GET /Players/{playerId}/Teams
时,将获得
Teams
资源的数组,这与调用
GET /Teams/{teamId}
时获得的资源完全相同。反向遵循相同的原理,调用call48ѭ可获得get44 get资源的数组。 在两个调用中,都不返回有关该关系的信息。例如,不返回任何“ 59”,因为返回的资源没有有关该关系的信息,而只有其自己的资源。 要处理n-n关系,请致电
GET /Players/{playerId}/TeamsPlayers
GET /Teams/{teamId}/TeamsPlayers
。这些调用将返回确切的资源
TeamsPlayers
TeamsPlayers
资源具有
id
playerId
teamId
属性,以及一些其他属性来描述这种关系。而且,它具有处理它们的必要方法。 GET,POST,PUT,DELETE等将返回,包含,更新,删除关系资源。
TeamsPlayers
资源实现了一些查询,例如
GET /TeamsPlayers?player={playerId}
返回由
{playerId}
标识的玩家具有的所有
TeamsPlayers
关系。按照相同的想法,使用
GET /TeamsPlayers?team={teamId}
返回在return73ѭ队中出战的所有
TeamsPlayers
。 在任一“ 74”调用中,返回资源“ 46”。返回与该关系有关的所有数据。 当呼叫
GET /Players/{playerId}/Teams
(或
GET /Teams/{teamId}/Players
)时,资源
Players
(或
Teams
)调用
TeamsPlayers
以使用查询过滤器返回相关的球队(或球员)。
GET /Players/{playerId}/Teams
的工作方式如下:      查找该玩家具有id = playerId的所有TeamsPlayers。 (
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\',}
此解决方案仅依赖REST资源。尽管可能需要一些额外的调用才能从玩家,团队或其关系中获取数据,但是所有HTTP方法都易于实现。 POST,PUT,DELETE非常简单明了。 每当创建,更新或删除关系时,
Players
Teams
资源都会自动更新。     ,        我知道有一个答案被标记为接受该问题,但是,这是我们如何解决先前提出的问题: 假设是PUT
PUT    /membership/{collection}/{instance}/{collection}/{instance}/
举例来说,以下所有操作都将产生相同的效果,而无需同步,因为它们是在单个资源上完成的:
PUT    /membership/teams/team1/players/player1/
PUT    /membership/players/player1/teams/team1/
现在,如果我们要为一个团队更新多个成员身份,我们可以执行以下操作(并进行适当的验证):
PUT    /membership/teams/team1/

{
    membership: [
        {
            teamId: \"team1\"
            playerId: \"player1\"
        },{
            teamId: \"team1\"
            playerId: \"player2\"
        },...
    ]
}
    ,         / players(是主资源) / teams / {id} / players(是一种关系资源,因此对1的反应不同) /会员(是一种关系,但语义上很复杂) / players / memberships(是一种关系,但语义上很复杂) 我喜欢2     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。