如何在RESTful API中创建/更新多对多关系

4
这是如何在我的API中将玩家添加到团队的方法:

PUT /teams/1/players/1/

我现在想把球员换到另一个队伍,我该怎么做呢?

你的第二个请求与第一个请求有何不同,以表明它是一种更改而不是添加。 - Garry
你的标题写着“多对多关系”,但是根据我理解你的解释,你的例子似乎是“一对多关系”,因为一个团队可以有多个球员,但每个球员只能属于一个团队... - Robert
3个回答

6
我可以尊敬地建议不要这样做。相反,将 /players/teams 都作为顶级资源。使用玩家上的属性来控制玩家所在的队伍。然后您可以通过 PUT 玩家并更新新的团队值来更新玩家的团队。
或者,创建一个包含所有球员-团队映射的新顶级资源,例如 '/team-memberships'。然后您可以查询 GET /team-memberships?teamId=7GET /team-memberships?playerId=2。您可以在此资源上发布和删除以添加和删除球员。
从概念上讲,玩家不是团队的子资源。玩家是一个独立的资源,与团队相关联。我认为上述任何一种方法都将给您带来更大的灵活性,更易于理解和使用。

将玩家分离成自己的资源,可以简化移动操作并遵循工具HTTP已经提供的概念,而不会误用任何概念。 - Roman Vottner

4
更改资源通常使用HTTP PUTHTTP PATCH(如果仅需执行部分更新,则使用后者)。但是,像PUT /teams/1/players/1?moveToTeam=2这样的结构存在一些语义问题,即用请求体中找到的负载替换当前表示。可选的查询参数是您在服务器端调用的方法,以将玩家从团队1移动到团队2。然而,HTTP PUT是幂等操作,这基本上意味着如果您执行相同的语句两次,则会产生相同的效果。但是,由于您正在从团队1中删除玩家并将当前用户的数据复制到新位置,因此调用相同的方法违反了HTTP PUT的幂等性质,因为连续调用将失败,因为没有可用的/teams/1/players/1资源或者没有可用的玩家内容,因此移动玩家的内容也将设置为空。因此,我不建议使用HTTP PUT

也许DELETE /teams/1/players/1?moveToTeam=2是最接近单个HTTP操作的方式,可以将一个玩家从一个团队移动到另一个团队。此请求成功地从团队1中删除了玩家,在调用之后不应再有该资源(幂等性),因此进一步的调用不会改变资源。该操作应返回一个200 OK,其中包括球员实体的新状态,其链接现在应指向其新位置。HTTP DELETE还允许移动资源,根据规范,尽管规范规定此资源在执行操作后不应可访问。

DELETE方法请求源服务器删除由Request-URI标识的资源。该方法可能被源服务器上的人为干预(或其他手段)覆盖。即使从源服务器返回的状态代码表明已成功完成操作,客户端也无法保证已执行该操作。但是,服务器不应指示成功,除非在给出响应时,它打算删除资源或将其移动到不可访问的位置

如果响应包含描述状态的实体,则成功的响应应该是200(OK);如果尚未执行操作,则为202(已接受);如果已执行操作但响应不包括实体,则为204(无内容)。(来源)

因此,我认为如果您尝试完全遵循RESTful,这个操作有点冒险。

因此,我建议将操作拆分为原子单元:

  • 获取用户当前的数据,以便移动并暂时存储 (GET /teams/1/players/1)
  • 从团队1中删除该球员 (DELETE /teams/1/players/1)
  • 将球员添加到所需的团队。使用暂存的数据作为请求的有效载荷 (POST /teams/2/players)
  • 为仍然引用GET /teams/1/players/1的用户创建重定向 (301 Moved Permanently),以便他们自动转发到GET /teams/2/players/n,其中n是球员的新ID。

每个操作都遵循HTTP规范中定义的规则,因此将请求分成原子部分应该没问题。


更新

尽管我同意 @EricStein 的观点,即将玩家分离到自己的资源中,因为这会极大地简化从团队1移动玩家到团队2的操作,但我也看了一下 PATCH 方法,它似乎比 DELETE 或将移动拆分成多个原子请求更合适。

PATCH 经常被误解为部分更新,只发送资源属性的新值到服务器,实际上并非如此。 PATCH 的结果可能相等,但是 PATCH 发送必要的步骤,服务器必须执行这些步骤才能将资源从一个状态转换为新状态。规范明确指出:

PATCH方法请求将请求实体中描述的一组更改应用于由请求URI标识的资源。这组更改以称为“patch文档”的格式表示,该格式由媒体类型标识。如果请求URI没有指向现有资源,则服务器可能会根据补丁文档类型(它是否可以逻辑地修改空资源)和权限等创建新资源。
PUT和PATCH请求的区别在于服务器处理包含实体以修改由请求URI标识的资源的方式。在PUT请求中,包含的实体被视为存储在原始服务器上的资源的修改版本,并且客户端请求替换存储的版本。然而,使用PATCH,包含的实体包含一组描述应如何修改当前驻留在原始服务器上的资源以生成新版本的指令。PATCH方法会影响由请求URI标识的资源,它还可能对其他资源产生副作用;即,应用程序可以通过应用PATCH来创建新资源或修改现有资源。(来源)

特别是最后引用的那一行指出它可能会产生副作用并创建新资源。因此,如果由于任何原因您无法更改您的模型,则PATCH可能是将球员从一支团队移动到另一支团队的最佳方式。该方法允许您发送服务器必须执行的必要步骤,以创建已移动球员的新资源,删除旧表示并在单个请求中建立永久前向到新资源。但是,此操作既不安全也不幂等!


我非常喜欢你的回答,但我认为你应该稍微分离一下角色。因为操作#1-#3是客户端操作,而#4是服务器操作...还有关于你第四点的另一件事:服务器如何识别新POST的玩家是哪个被DELETE了(并创建#301规则)? - Robert
@Robert 的观点很有道理。第4点肯定是一些服务器任务,但服务器可能不知道以前的 /teams/1/players/1 现在变成了 /teams/2/players/n,因此移动资源的用户必须通知服务器有关“移动”的信息。这可以通过将#2与#3交换并向 DELETE 操作添加一个参数来解决,该参数通知服务器应创建重定向到 #2 返回的位置(尽管我认为这有点违反删除语义),或者发送一个 PUT 请求作为#4,创建指向已移动资源的重定向。 - Roman Vottner

0

做类似于POST /teams/1/players/5/transfers这样的事情怎么样?这将创建一个“团队球员转会”的效果。在url中提供了TeamPlayerid,而正文可以是{ new_team_id: 2 }之类的东西。然后服务器可以以任何它喜欢的方式执行“转移”。

“转移”可能是数据库中的对象,也可能不是(在我的情况下不是)。这种结构对我来说似乎是符合RESTful的,而且阅读起来也很直观。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接