处理资源之间双向关系的RESTful方法

8

假设我想设计一个关于歌曲、专辑和艺术家的REST API(实际上,像我之前有1312414人这样做过)。

歌曲资源总是与其所属的专辑相关联。相反,专辑资源与其包含的所有歌曲相关联。这些关联通过链接在资源表示中表示。

因此,表示将类似于以下内容:

{
   song: 'xyz',
   links: [
      { rel: 'album', url: '.../albums/abc' }
   ]
}

{
   album: 'abc',
   links: [
      { rel: 'song', url: '.../songs/xyz' },
      { rel: 'song', url: '...' },
      { rel: 'song', url: '...' },
      { rel: 'song', url: '...' }
   ]
}

假设我想要实现这一点(也许问题出在“假设”上),那么我该如何设计我的API,使得创建专辑或歌曲资源不会对先前存在的歌曲或专辑资源产生副作用?
这是一种鸡生蛋的问题。如果我先创建歌曲资源(POST /songs/),然后再创建专辑资源(POST /albums/),那么歌曲资源将作为专辑创建的一部分而被修改(这违反了REST原则),因为两个资源之间的关联正在服务器上更新。同样,对于我先创建专辑,然后创建歌曲的情况也是如此。
我猜我可以通过避免在服务器上产生副作用并将双向关系的管理负担传递给客户端来避免整个问题。
此外,我不希望专辑和歌曲被全面地原子化创建。
我现在唯一能想到的就是将上述副作用包含在我的API语义中,通过响应资源创建并提供一个包含已由请求导致修改的资源链接列表的表示来使副作用显式化,但这仍然不符合REST原则。

创建一首歌曲,如果它有专辑字段,并且该专辑不存在,则创建该专辑。创建一个专辑,如果它有不存在的歌曲,则创建该歌曲。 - zzzzBov
一个看待这个问题的方式是,每个专辑和歌曲都是封装的资源,链接只是将它们彼此连接起来的图形。改变链接会改变资源之间的关系,但资源本身并不会实际改变。 - abraham
3
“歌曲资源在专辑创建过程中被修改了(这是不符合REST原则的)。”这个“原则”是哪个?能提供一个链接吗?根据我的经验,更新资源总会对其他资源产生副作用。 - Darrel Miller
Darrel的评论非常准确。再次强调:更新资源可能会对其他资源产生副作用。如果您的数据库强制执行引用完整性,则必然会发生这种情况。不要与其抗争,而是利用它。 - Brian Kelly
1个回答

1

REST并未阐述一种资源的状态修改如何不能改变其他资源的状态。REST最接近这么做的概念是幂等操作,仅表明重复执行它们会导致相同的状态。

所以,在您的情况下,一个 Song 资源能够有效地将自己添加到一个 Album 资源中,并没有本质上的错误。同样,一个 Album 资源能够声明一个 Song 资源是它的一部分,也没有本质上的错误。

现在,根据您的业务需求,您可能想要更改如何表示歌曲/专辑或允许以 m/m 方式而不是 1/1 相关的歌曲/专辑。原因是,取决于您如何构建数据和选择系统中的可寻址单位(即可寻址资源),实际上模拟了不同的数据关系,这我认为是您面临的问题的关键。

在您的系统中,将SongsAlbums作为单独的资源,强制执行更严格的关系(如1/1而不是m/m),需要在内容类型中进行更多的工作和规范。您必须处理两个不同专辑都认为它们包含单个歌曲的情况,但1/1关系不允许这样做。
如果您有一个Album对象明确地包含或拥有Song对象,则问题就会减少,因为Album只能操作自己的歌曲,而不能操作任何其他Album的歌曲。然而,这会改变数据模型,因为Songs不再是一流公民,而是属于拥有它们的专辑下面。
这是整个问题的关键点... 您必须决定谁拥有关系
双方(AlbumSong)都拥有关系并没有什么问题。这对于m/m关系非常有效,但对于1/1关系,这需要更多的管理开销(即实际上有其他东西拥有关系)。

如果您想要一个没有所有额外开销的1/1关系,那么您必须让参与的实体中的一个拥有该关系,这意味着只有通过拥有该关系的实体才能更改它...。


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