RESTful API URI设计

13
我在寻找有关RESTful API的URI设计方向。我将有几个嵌套的链接资源,并且目前已经设计了类似于这篇文章的URI:Hierarchical RESTful URL design
以下示例不是我正在构建的内容,但我认为很好地说明了我的情况。(假设一部节目只能属于一个网络)。
/networks [GET,POST]
/networks/{network_id} [GET,PUT]
/networks/{network_id}/shows [GET,POST]
/networks/{network_id}/shows/{show_id} [GET,PUT]
/networks/{network_id}/shows/{show_id}/episodes [GET,POST]
/networks/{network_id}/shows/{show_id}/episodes/{episode_id} [GET,PUT]

我的情况将通过关联进一步扩展两个级别,但所有的关联都是一对多。我正在考虑将其改为类似于以下内容:

/networks [GET,POST]
/networks/{network_id} [GET,PUT]
/networks/{network_id}/shows [GET,POST]

/shows [GET]
/shows/{id} [GET,PUT]
/shows/{id}/episodes [GET,POST]

/episodes [GET]
/episodes/{id} [GET,PUT]

我的问题是:

  1. 第二个示例是否是有效的REST设计?
  2. 我应该考虑实现两个路径吗?

请记住,您最终决定RESTful API的结构。使用它的是您的客户。我认为两者都不错,尽管显然第一个更好地说明了网络、节目和剧集之间的层次关系。但是,如果URI变得非常长,将这些实体分开放在 / 下也不是不可行的。您应该问自己,哪些资源实际上会被最多使用。如果是剧集和节目,那么将它们放在基础下是很有意义的。 - thatidiotguy
6个回答

6
第二个例子对我来说看起来很好。URL描述了资源,正确的HTTP动词正在使用。
如果有多个指向相同资源的URL,那么也完全可以,只要有意义就行。但更重要的是,确保资源包含将节目连接到网络、剧集连接到节目等的元素。

+1:我个人更喜欢使用 xlink:href 属性,但 <link> 元素也不错。 - Donal Fellows
属性在XML/XHTML中运作良好,但某些表示形式(如JSON)不支持它们。无论如何,重要的是利用超媒体使服务真正符合RESTful原则。 - Dmitry S.

3
这里的真正问题是:您的第二个示例是否符合URI标准?URI标准规定,路径包含分层部分,查询包含非分层部分,但据我所知,它并没有说明如何在您的情况下设计URI结构。REST统一接口约束有一个HATEOAS部分,这意味着您应该发送指向上下级资源的链接,并使用元数据注释这些链接,以便客户端可以处理它们,从而了解链接的内容。因此,在实践中,URI结构并不重要...
翻译的内容: GET /shows/123
{
    "label": "The actual show",
    "_embedded": {
        "episodes": [
            {
                "label":  "The first episode of the actual show",
                "_embedded": {
                    "associations": [
                        //...
                    ]
                },
                "_links": {
                    "self": {
                        "label": "Link to the first episode of the actual show",
                        "href": "/episodes/12345"
                    },
                    "first": {
                        "href": "/episodes/12345"
                    },
                    "duplicate": {
                        "href": "/networks/3/shows/123/episodes/12345"
                    },
                    "up": {
                        "label": "Link to the actual show",
                        "href": "/shows/123"
                    },
                    "next": {
                        "label": "Link to the next episode of the actual show"
                        "href": "/episodes/12346"
                    },
                    "previous": null,
                    "last": {
                        "href": "/episodes/12350"
                    }
                }
            }//,
            //...
        ]
    },
    "_links": {
        "self": {
            "label": "Link to the actual show",
            "href": "/shows/123"
        },
        "duplicate": {
            "href": "/networks/3/shows/123"
        },
        "up": {
            "label": "Link to the actual network",
            "href": "/networks/3"
        },
        "collection": {
            "label": "Link to the network tree",
            "href": "/networks"
        },
        "next": {
            "label": "Link to the next show in the actual network",
            "href": "/shows/124"
        },
        "previous": {
            "label": "Link to the previous show in the actual network",
            "href": "/shows/122"
        }
    }
}

现在,这只是一个基于HAL+JSON和IANA链接关系的beta版示例。但是你也可以使用带有RDF词汇表(例如schema.org+hydra)的JSON-LD。这个示例仅涉及层次结构(上、第一个、下一个、上一个、最后一个、集合、项等),但你应该添加更多元数据,例如指向网络、节目和剧集的链接等。因此,客户端可以从元数据中了解内容,并且例如可以使用链接自动导航。这就是REST的工作原理,因此URI结构对客户端并不重要。(如果你想使响应更紧凑,也可以使用紧凑型URI和URI模板。)


1

假设您在以下层次结构中具有一对多的关系:

network --> shows --> episodes

我认为第二个设计没有提供足够的信息给服务器端来处理您的请求。例如,如果您有以下数据:

Network id  show_id episode_id
    1         1        1
    1         2        1
    1         1        2

第一种冗长的设计将在HTTP请求中提供足够的信息来获取数据:/networks/1/shows/1/episodes/1

相反,第二种设计将有:

/episodes/1 

在第二个设计中,服务器端无法知道您的数据是指行1还是行2。
回答您的问题:
1. 依我的看法,第二个设计可能不适用于您的示例,可以通过传递查询参数来解决。 2. 我认为第一个设计已经足够完整。
更新:请忽略我之前的回答
1. 第二个设计对于您的示例是有效的REST设计。 2. 只有第二个设计也足够完整。
此外:
/networks
/networks/{id}

/shows
/shows/{id}

/episodes
/episodes/{id}

应该有足够数量的REST URL。

换句话说,以下URL将是冗余的:

/networks/{network_id} [GET,PUT]
/networks/{network_id}/shows [GET,POST]

/shows/{id}/episodes [GET,POST]

亲爱的Vikram,如果我理解不正确,请纠正我,你是否建议使用3列组合主键来纠正设计中的问题? - Alessandro Oliveira
@AlessandroOliveira,感谢您的评论。您的评论帮助我理解了问题中第二个设计也是有效的设计!回答您的问题:三列复合主键只是一种表示方法,用于理解这三个分层实体之间的关系。我并不是要推荐它... - Vikram
另外一件让我非常关注的事情是将主键暴露为对象ID,即使系统已经有了身份验证和授权措施,这些实现决策也不应以任何方式暴露。但由于我是UUID的支持者,我的立场可能会被认为有点偏见。 - Alessandro Oliveira
@AlessandroOliveira 我同意你的观点。 - Vikram
在我看来,您第一次尝试就做对了。如果没有了解节目信息就不能确定某一集内容,那么拥有一个/episodes路由就没有意义。仅当它们本身都具有唯一的ID时,但即使如此,我也会质疑其实用性。 - DanMan

1

URI指的是“任何可以被命名的信息”。

你的问题涉及到领域知识,只能由了解你所使用的资源的人来回答。

在猜测你的领域时,首先想到的问题是,“show”是否真的取决于“network”?

在你的领域中,“网络”是什么? “show”和“network”之间的关系是什么?它只是一个播出该节目的人吗?还是更多地涉及制作信息?

我认为你的第二个例子更加合适。


你在前半段让我信服,但最后一句话破坏了它。如果没有提供节目ID就不存在剧集,那么从根节点暴露它们就没有太多意义。尽管电视台可能不存在,但节目确实存在,因此有道理这样做。 - DanMan

0

我认为第二个选项是可以的,但如果您想要验证关系,我会考虑第一个选项。例如,当您获取具有不同网络的剧集时,这可能意味着在您的请求之前修改了该剧集,因此您可能需要响应422,其他服务也是如此。通过这种方式,您可以确保您想要处理的实体涉及其父级。

附注:对于我的英语表示抱歉。


0

我认为我们应该尽可能保持REST API的URL简单。

例如:https://www.yoursite.com/api/xml/1.0/

这里我以XML API 1.0版本为例。请记得在未来更新中使用API的版本。

您可以检查客户端请求的方法。

e.g. tag

<method>getEpisodes</method>

4
这看起来不是特别符合REST规范,而是更像SOAP(如果你确实使用的是SOAP也无妨,但SOAP和REST在构建应用方面有很大的区别)。 - Donal Fellows
我的主要关注点是API的版本。例如,我的稳定API发布版本是1.2,我的客户在他们的应用程序中也使用相同的版本。现在我们增强了我们的API(版本1.4),并且弃用了一些在版本1.2中支持的功能。使用版本1.2的客户将会因为被弃用的功能而出现错误。请让我知道您的想法。 - kwelsan

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