REST设计原则:引用相关对象与嵌套对象

时间:2019-03-22 19:59:16

标签: rest

我和我的团队正在重构REST-API,我遇到了一个问题。  为简洁起见,让我们假设我们有一个包含4个表的SQL数据库:教师,学生,课程和教室。 现在,通过引用相关项目的URL,可以在REST-API中表示项目之间的所有关系。例如,对于一门课程,我们可以有以下内容

{ "id":"Course1", "teacher": "http://server.com/teacher1", ... }

此外,如果问一个课程列表,认为有一个GET是对/courses的调用,我会得到一个参考列表,如下所示:

{
   ... //pagination details
  "items": [
   {"href": "http://server1.com/course1"},
   {"href": "http://server1.com/course2"}...
 ]
}

这一切都很好,而且很干净,但是如果我想列出所有带有老师姓名的课程标题,并且我有2000门课程和500名老师,则必须执行以下操作:

  • 大约2500个查询仅用于读取数据。
  • 实现教师与课程之间的衔接
  • 使用缓存等进行优化,以便我尽快完成。

我的问题是,此方法通过成千上万的REST-API调用创建了大量的网络流量,并且我必须重新实现数据库将更有效地执行的自然联接。 同事们说,这是实现REST-API的标准方法,但是相对简单的查询就变得很麻烦。

因此,我的问题是:     1.如果我们在课程中嵌套教师信息,那是不对的。     2.是否应列出物品,例如GET /courses返回引用列表还是项目列表?

编辑:经过一番研究,我会说我所想到的模型主要对应于jsonapi.org中所示的模型。这是个好方法吗?

3 个答案:

答案 0 :(得分:1)

  

我的问题是,此方法通过成千上万的REST-API调用创建了大量的网络流量,并且我必须重新实现数据库将更有效地执行的自然联接。同事们说,这是实现REST-API的标准方法,但是相对简单的查询就变得很麻烦。

您是否实际测量了每个请求产生的开销?如果没有,您怎么知道开销会太大?从面向对象的程序员的角度来看,独自执行每个调用听起来可能很糟糕,但是您的设计缺少一项重要的资产,可以帮助Web扩展到当前的大小:

缓存可以在多个级别上进行。您可以在API级别上执行此操作,或者客户端可以执行某些操作,或者中介服务器可以执行此操作。 Fielding甚至疯狂地限制了REST!因此,如果您要遵守REST体系结构的哲学,则还应该支持响应的缓存。缓存有助于减少必须由单个服务器计算甚至处理的请求的数量。在无状态通信的帮助下,您甚至可以引入大量服务器,这些服务器全部针对数十亿个请求执行计算,这些请求充当客户端的一个内聚系统。中间缓存可以进一步帮助减少实际到达服务器的请求数量。

作为一个整体的URI(包括任何路径,矩阵或查询参数)实际上是缓存的键。在接收到GET请求后,即,应用程序检查其当前缓存是否已包含该URI的存储响应,如果存储的数据“足够新”,则代表服务器将存储的响应直接返回给客户端。 。如果存储的数据已超过新鲜度阈值,它将丢弃存储的数据,并将请求路由到下一跳(可能是实际的服务器,可能是进一步的中介)。

有时,找到适合缓存的资源有时可能并不容易,尽管大多数数据并不会很快改变以至于完全忽略了缓存。因此,至少应该普遍引入缓存,尤其是API产生的更多流量。

虽然某些媒体类型(例如HAL JSONjsonapi,...)允许您将从相关资源收集的内容嵌入到响应中,但是嵌入内容有一些潜在的缺点,例如:

  • 由于将快速变化的数据与静态数据混合在一起,因此缓存的利用率可能较低
  • 服务器可能会计算客户端不需要的数据
  • 一台服务器计算整个响应

如果相关资源仅链接到而不是直接嵌入,则客户端肯定必须触发进一步请求以获取该数据,尽管实际上它更可能(部分地)由缓存提供服务,该缓存如前所述现在在整个帖子中进行了两次,减少了服务器上的工作量。除此之外,一个积极的副作用可能是,您可以了解客户真正感兴趣的内容(如果您运行中介缓存)。

  
      
  1. 如果我们在课程中嵌套教师信息,那是不对的。
  2.   

这没错,但是如上所述可能并不理想

  
      
  1. 是否应列出项目,例如GET / courses返回引用列表还是项目列表?
  2.   

这取决于。没有对与错。

由于REST只是Web中使用的交互模型的概括,因此基本上相同的概念也适用于REST。根据“项目”的大小,返回项目内容的简短摘要并添加指向项目的链接可能会有所帮助。相似的事情也在网络上完成。对于参加课程的学生的列表,这可能是名称及其入学编号,并且可能会要求该学生提供更多链接的链接细节,以提供实际的链接,客户端可以使用此语义上下文。决定调用这种URI是否有意义。

此类链接关系名称通过IANADublin Coreschema.org之类的通用方法或RFC 8288 (Web Linking)中定义的自定义扩展名进行了标准化。对于上述课程的学生名单,您可以使用about关系名称来提示客户,可以通过以下链接找到有关当前项目的更多信息。如果要启用分页功能,则firstnextprevlast的使用可以并且可能也应使用,依此类推。

这实际上是HATEOAS的全部内容。将数据链接在一起并赋予它们有意义的关系名称,以跨越资源之间的一种语义网。通过将事物简单地嵌入到响应中,这样的语义图可能难以构建和维护。

最后,基本上可以归结为实现选择,是要嵌入还是引用资源。我希望,我能对缓存的有用性及其带来的好处(特别是在大型系统上)以及为URI提供链接关系名称的好处有所了解,以增强所使用关系的语义上下文。在您的API中。

答案 1 :(得分:1)

  

我的问题是,此方法通过成千上万的REST-API调用创建了大量的网络流量,并且我必须重新实现数据库将更有效地执行的自然联接。同事们说,这是实现REST-API的标准方法,但是相对简单的查询就变得很麻烦。

您的同事们失去了密谋。

这是您的试探法-您将如何在网站上支持此用例?

您可能会通过定义一个新的网页来完成此工作,该网页将生成您需要的报告。您将运行查询,结果集将生成一堆HTML,然后按一下!客户以标准化的方式获得所需的信息。

REST-API是同一回事,更强调机器的可读性。创建具有架构的新文档,以便客户可以理解返回给他们的文档的语义,告诉客户如何找到该文档的目标uri,然后瞧瞧。

创建用于处理新用例的新资源是REST的 normal 方法。

答案 2 :(得分:0)

是的,我完全认为您应该设计类似于jsonapi.org的东西。根据经验,我会说“首选需要较少网络呼叫的解决方案”。如果网络呼叫的数量减少数量级,则尤其如此。 当然,如果请求/响应的大小变得不合理,它并不会消除限制请求/响应大小的需要。

现实生活中的解决方案必须具有适当的平衡。只要可以使用Clean API,它就很好。

所以在您的情况下,我会这样:

GET /courses?include=teachers

GET /courses?includeTeacher=true

GET /courses?includeTeacher=brief|full

在最后一个响应中,只能包含brief的教师ID和full的完整教师详细信息。