API 设计:缓存“部分”嵌套对象

Posted

技术标签:

【中文标题】API 设计:缓存“部分”嵌套对象【英文标题】:API Design: Caching “partial” nested objects 【发布时间】:2016-12-13 17:56:39 【问题描述】:

假设我们的学校有一些数据,包括姓名和学生名单,学生有一些数据,包括他们注册的课程和对他们学校的引用。在客户端:

我想显示一个显示学校信息的屏幕,其中包括按姓名列出的所有学生的列表。 我想显示一个屏幕,其中显示有关学生的信息,包括他们学校的名称和他们正在学习的课程的名称。 我想缓存这些信息,这样我就可以显示相同的屏幕,而无需等待新的提取。我应该能够从一个学校到另一个学生再回到学校,而无需再次去学校。 我希望每个屏幕只显示一次提取。从学校页面转到学生页面可能需要单独获取,但我应该能够在一次获取中显示具有完整学生姓名列表的学校。 我想避免重复数据,这样如果学校名称发生变化,一次获取更新学校将导致正确的名称同时显示在学校页面和学生页面上。

有没有什么好的方法来做这一切,还是必须解除一些限制?

第一种方法是使用 API 来执行以下操作:

GET /school/1


    id: 1,
    name: "Jefferson High",
    students: [
        
             id: 1
             name: "Joel Kim"
        ,
        
             id: 2,
             name: "Chris Green"
        
        ...
    ]



GET /student/1


    id: 1,
    name: "Joel Kim",
    school: 
        id: 1,
        name: "Jefferson High"
    
    courses: [
        
             id: 3
             name: "Algebra 1"
        ,
        
             id: 5,
             name: "World History"
        
        ...
    ]

这种方法的一个优点是,对于每个屏幕,我们只需进行一次提取。在客户端,我们可以规范化学校和学生,以便他们通过 ID 相互引用,然后将对象存储在不同的数据存储中。但是,嵌套在school 中的student 对象并不是一个完整的对象——它不包括嵌套的课程,或者对学校的引用。同样,student 内部的 school 对象没有所有在读学生的列表。在数据存储中存储对象的部分表示会导致客户端出现一堆复杂的逻辑。

我们可以存储学校和学生及其嵌套的部分对象,而不是规范化这些对象。然而,这意味着数据重复——杰斐逊高中的每个学生都会嵌套有学校的名称。如果在获取特定学生之前学校名称发生更改,那么我们会在其他地方显示该学生的正确学校名称,但在其他地方显示错误名称,包括在“学校详细信息”页面上。

另一种方法可能是将 API 设计为仅返回嵌套对象的 ID:

GET /school/1


    id: 1,
    name: "Jefferson High",
    students: [1, 2]



GET /student/1


    id: 1,
    name: "Joel Kim",
    school: 1,
    courses: [3, 5]

我们总是拥有对象的“完整”表示及其所有引用,因此将这些信息存储在数据存储客户端非常容易。但是,这需要多次获取才能显示每个屏幕。要显示有关学生的信息,我们必须先获取该学生,然后再获取他们的学校和课程。

是否有更聪明的方法可以让我们只缓存每个对象的一个​​副本,并防止多次提取以显示基本屏幕?

【问题讨论】:

我喜欢这个问题,并发现讨论 here 在思考它时很有帮助。您的问题的标题提到了缓存,但正文没有。如果您使用第二种(标准化)方法,您的浏览器可以自己缓存请求,从而为您节省一些访问服务器的次数。 @this-vidor 感谢您的链接!我什至没有考虑浏览器缓存与仅在 Redux 状态的键值存储中保留对象(“缓存”)。我认为浏览器缓存和 Redux 状态如何一起使用将是一个实现细节,不一定会改变原始问题的答案。 不管怎样,我正在使用 Redux 和一个 Web 浏览器,但是相同的设计约束可以很容易地应用于使用其他一些客户端数据存储和一些其他客户端数据存储的任何其他客户端(例如本机应用程序)其他带有缓存的 HTTP 客户端。 【参考方案1】:

您可能会混淆两个概念:存储表示。您可以返回一个非规范化的表示(您建议的第一个选项)而不也将这些“部分”对象存储在您的数据库中。

所以我建议尝试返回非规范化表示,但将它们标准化存储(如果您使用的是关系数据库)。

另外,还有一个改进建议:您可能希望在表示中使用正确的 URI 而不是 Id。您可能希望客户端知道从“哪里”获取该对象,因此只提供 URI 会更容易。否则客户端需要弄清楚如何从 Id 中生成 URI,而这通常最终会在客户端中被硬编码,这在 REST 中是不允许的。

【讨论】:

这对我来说似乎是一个非常合理的方法。因此,在示例中,当我们想要显示学校视图时,我们会为学校获取并存储规范化的学校而不存储任何学生(因为学生对象只是“部分”包含在表示中)。但是,我们仍然会使用部分学生来填充学校视图中的学生列表。我看到的一个问题是,如果我们要重新访问这个学校视图,我们必须再次进行提取,因为学校将在数据存储中,但引用的学生不会。关于如何处理的任何想法? 浏览器会/应该为你缓存整个响应(如果服务器给出了必要的缓存控制指令),所以你不必担心那。您可以重复请求,您应该会收到一个缓存文档,或者服务器可能会生成一个条件GET,您仍然可以在服务器端对其进行优化。您是在客户端从头开始构建缓存吗? 是的,也许天真地如此。我使用 Redux 将这些对象中的每一个存储在键值存储中(因此获取学校 1 会将对象存储在 state.schools[1] 中)。似乎浏览器缓存在这里会有所帮助。如果我只是依赖缓存,我不确定在键值存储中仍然保存对象是否有任何意义(甚至完全使用 Redux)。 说实话,我不了解 Redux,但我确实有 RESTful HTTP 和服务器端的经验。 HTTP 缓存 (tools.ietf.org/html/rfc7234) 非常强大,包括过期、条件重新验证、直写缓存失效等。它在每个浏览器中都是开箱即用的。它可能已经涵盖了您正在寻找的大部分(如果不是全部)功能。 谢谢你——这帮助很大。我需要睡在上面/再想一想,但我开始认为这可能是这里的路。

以上是关于API 设计:缓存“部分”嵌套对象的主要内容,如果未能解决你的问题,请参考以下文章

php WordPress Transients API,用于在不同时间使网页缓存部分到期的代码

设计模式 组合模式

如何将 JSON 对象的嵌套部分转换为点链式平面 JSON?

C#设计模式09——组合模式的写法

在 Web API 中缓存数据

ractive js:与模板中的#each键部分关联的嵌套对象列表的直接dom插入排序/顺序