嵌套的REST网址和父ID,哪个设计更好?


20

好的,我们有两个资源:AlbumSong。这是API:

GET,POST /albums
GET,POST /albums/:albumId
GET,POST /albums/:albumId/songs
GET,POST /albums/:albumId/songs/:songId

我们知道我们讨厌某首歌,Susy例如。我们应该在哪里search采取行动?

另一个问题。好吧,现在更真实了。我们打开专辑1并加载所有歌曲。我们创建了JS对象,每个对象都保存歌曲数据,并具有如下几种方法:removeupdate

歌曲对象具有ID,名称和内容,但是不知道它属于哪个父对象,因为我们通过查询来检索歌曲列表,因此每个父对象都返回父ID并不是很好。我错了吗?

因此,我看到的解决方案很少,但我不确定。

  1. 将父ID设为可选-作为get-parameter。我目前使用的是这种方法,但我觉得它很丑。

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. 混合动力车 现在方便了,因为我们需要唱片集ID来执行OPTIONS查询以获取元数据。

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. 返回每个资源实例的完整URL。API是相同的,但是我们会得到如下歌曲:

    id: 5
    name: 'Elegy'
    url: /albums/2/songs/5
    

    我听说这种方法称为HATEOAS。

  4. 所以...提供父母身分证

    id: 5
    name: 'Elegy'
    albumId: 2
    

哪个更好?还是我很笨?提出一些建议,伙计们!

Answers:


31

我们应该把搜索动作放在哪里?

在中GET /search/:text。这将返回一个包含匹配项的JSON数组,每个匹配项都包含其所属的相册。这是有道理的,因为客户可能对曲目本身不感兴趣,而对整个专辑感兴趣(想像一下,您正在搜索的歌曲与您记得的名字在同一张专辑中,所以您相信这是一首歌曲)。

每次返回父ID都不好。我错了吗?

单个曲目可以包含专辑。如果您可以通过专辑或搜索(此处没有专辑)来获取曲目,这将确保曲目表示的统一性。

哪个更好?

如前所述,包括专辑是有意义的。尽管在某些情况下第三点(带有相对URI)可能很有趣(您不必考虑URI的形成方式),但它的缺点是没有明确提供专辑。第四点纠正了这一点。如果您看到在响应中包含相对URI的好处,则可以将点3和4相结合。

还是我很笨?

选择好的URI不是一件容易的事,特别是因为没有一个正确的答案。如果您与API同时开发客户端,则可以帮助您更好地可视化如何使用API​​。话虽这么说,然后其他人可能会喜欢您在开发API时没有想到的其他用法。

可能有问题的一个方面是您如何内部组织数据,即层次结构的使用。从您的评论中,您想知道应该包含什么对的响应GET /artist/1/album/10/song/3/comment/23,它显示出非常面向树的愿景。以后扩展系统时,这可能会导致一些问题。例如:

  • 如果一首歌没有专辑怎么办?
  • 如果一张专辑有几位歌手怎么办?
  • 如果您想添加一项功能,使评论相册成为可能?
  • 如果应该有评论的评论该怎么办?
  • 等等

从本质上讲这就是我在博客中解释的问题:树表示形式存在太多限制,无法在许多情况下有效使用。

如果破坏层次结构会怎样?让我们来看看。

  1. GET /albums/:albumId返回一个JSON,其中包含有关专辑的元信息(例如发布该专辑的年份或显示专辑封面的JPEG的URI)和一个曲目数组。例如:

    GET /albums/151
    {
        "id": 151,
        "gid": "dbd3cec7-b927-423f-894b-742c4c7b54ce",
        "name": "Yellow Submarine",
        "year": 1969,
        "genre": "Psychedelic rock",
        "artists": ["John Lennon", "Paul McCartney", ...],
        "tracks": [
            {
                "id": 90224,
                "title": "Yellow Submarine",
                "length": "2:40"
            },
            {
                "id": 83192,
                "title": "Only a Northern Song",
                "length": "3:24"
            }
            ...
        ]
    }
    

    例如,为什么要包括每个音轨的长度?因为我想象显示专辑的客户可能会对按标题列出曲目感兴趣,但同时也会显示每首曲目的长度,大多数客户都会这样做。另一方面,我可能不会为每个曲目都显示作曲家或艺术家,因为我认为在此级别上不需要此信息。显然,您的选择可能有所不同。

  2. GET /tracks/:trackId返回有关特定轨道的信息。由于不再存在层次结构,因此您无需猜测专辑或艺术家:您真正需要知道的唯一是轨道本身的标识符。

    甚至可能没有?如果可以使用名称指定它GET /tracks/:trackName怎么办?

    GET /tracks/Only%20a%20Northern%20Song
    {
        "id": 83192,
        "gid": "8d9c4311-9d7b-40a4-8aeb-4fe96247fe2b",
        "title": "Only a Northern Song",
        "writers": ["George Harrison"],
        "artists": ["John Lennon", "Paul McCartney", "Ringo Starr"],
        "length": "3:24",
        "record-date": 1967,
        "albums": [151, 164],
        "soundtrack": {
            "uri": "http://audio.example.com/tracks/static/83192.mp3",
            "alias": "Beatles - Only a Northern Song.mp3",
            "length-bytes": 3524667,
            "allow-streaming": true,
            "allow-download": false
        }
    }
    

    现在仔细看albums; 你看到了什么?是的,不是一张,而是两张专辑。如果您具有层次结构,则不能这样做(除非您重复记录)。

  3. GET /comments/:objectGid。您可能在响应中发现了丑陋的GUID。这些GUID使得跨数据库识别实体成为可能,以执行可应用于专辑,艺术家或曲目的任务。如评论。

    GET /comments/8d9c4311-9d7b-40a4-8aeb-4fe96247fe2b
    [
        {
            "author": {
                "id": 509931,
                "display-name": "Arseni Mourzenko"
            },
            "text": "What a great song! (And I'm proud of the usefulness of my comment)",
            "concerned-object": "/tracks/83192"
        }
    ]
    

    注释引用了相关的对象,从而可以在其上下文之外访问注释时(例如,通过审核最新注释时GET /comments/latest)转到该对象。

请注意,这并不意味着您应该避免在API中使用任何形式的层次结构。在某些情况下,这是有道理的。根据经验:

  • 如果资源在其父资源的上下文之外毫无意义,请使用层次结构。

  • 如果资源可以单独生存(1)或(2)在不同类型的父资源的上下文中生存,或者(3)有多个父资源,则不应使用层次结构。

例如,文件的行在文件上下文之外没有任何意义,因此:

GET /file/:fileId

和:

GET /file/:fileId/line/:lineIndex

没事。


是的,从搜索中,我也可以返回完整的专辑信息,这将成为另一个资源- SongSearchResult,我想很好。但是URL是什么呢?我应该提供parentID每个对象并将其用作GET参数还是url的正常部分吗?如果我的深度大于2怎么办?/artist/1/album/10/song/3/comment/23-提供艺术家,专辑和歌曲的每一个ID都是很疯狂的comment,但是我听说这是一个可行的方法,但是不是令人讨厌吗?
dt0xff

@ dt0xff:我编辑了答案。我认为它现在应该为您提供如何避免深度的清晰图像。
Arseni Mourzenko 2015年

是的,现在很明显,为每个资源(除了某些行或其他功能对象之外)实现入口点要容易得多,而无需通过url将其添加到父级。谢谢,您使我相信我的选择是正确的,“通用方法”(实际上,很多嵌套的东西restangular都建立在上面)不是那么好。
dt0xff 2015年

好答案。我有一些反对意见。“如果一张专辑有几个艺术家怎么办?” 由于二进制关系URI-> resource是右唯一的(多对一),因此不同的URI可以标识相同的资源。所以这些URI /artists/foo/albums/qux/artists/bar/albums/qux能很好地识别同一专辑的资源。换句话说,URI中的路径组件表示层次结构,而不必表示层次结构,这使其不仅适合表示类别,还适合表示标签。
Maggyero

1
……“这本质上是我在博客中解释的问题:树表示形式有太多限制,无法在许多情况下有效使用。” 所以不,这不是问题。“如果您想添加一项功能,使评论相册成为可能,该怎么办?” 也不是问题:/artists/foo/albums/qux/comments/7。“如果要发表评论该怎么办?” 同样:/artists/foo/albums/qux/song/5/comments/2/comments/8
Maggyero
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.