API 设计:缓存“部分”嵌套对象
API Design: Caching “partial” nested objects
假设我们的学校有一些数据,包括姓名和学生名单,学生有一些数据,包括他们注册的课程和对他们学校的引用。在客户端:
- 我想展示一个显示有关学校的信息的屏幕,其中包括所有学生姓名的列表。
- 我想展示一个显示学生信息的屏幕,包括他们的学校名称和他们所修课程的名称。
- 我想缓存此信息,这样我就可以显示同一屏幕而无需等待新的提取。我应该能够从学校到学生再回到学校,而无需再次去学校。
- 我想显示每个屏幕只有一次抓取。从学校页面转到学生页面可以单独获取,但我应该能够在一次获取中显示包含完整学生姓名列表的学校。
- 我想避免重复数据,这样如果学校名称发生变化,一次获取更新学校将导致在学校页面和学生页面上显示正确的名称。
是否有一个好的方法来完成所有这些,或者是否必须取消某些限制?
第一种方法是让 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
对象没有所有参加学生的列表。在数据存储中存储对象的部分表示会导致客户端出现一堆复杂的逻辑。
我们可以存储学校和学生及其嵌套的部分对象,而不是规范化这些对象。然而,这意味着数据重复——Jefferson High 的每个学生都会嵌套学校的名称。如果学校名称在为特定学生进行提取之前发生了变化,那么我们会为该学生显示正确的学校名称,但在其他地方显示错误的名称,包括 "school details" 页面。
另一种方法是将 API 设计为仅 return 嵌套对象的 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]
}
我们总是有 "complete" 对象的表示及其所有引用,因此很容易将此信息存储在数据存储客户端中。但是,这需要多次提取才能显示每个屏幕。要显示有关学生的信息,我们必须获取学生,然后获取他们的学校以及他们的课程。
是否有更智能的方法允许我们只缓存每个对象的一个副本,并防止多次提取以显示基本屏幕?
您可能混淆了两个概念:存储和表示。你可以返回一个非规范化的表示(你建议的第一个选项)而不用也将那些"partial"对象存储在你的数据库中。
所以我建议尝试 return 非标准化表示,但将它们标准化存储(如果您使用的是关系数据库)。
还有一个改进建议:您可能希望在表示中使用正确的 URI 而不是 ID。您可能希望客户端知道 "where" 从中获取该对象,因此只提供 URI 会更容易。否则,客户端需要弄清楚如何从 Id 中生成 URI,而这通常最终会在客户端中进行硬编码,这在 REST 中是不允许的。
假设我们的学校有一些数据,包括姓名和学生名单,学生有一些数据,包括他们注册的课程和对他们学校的引用。在客户端:
- 我想展示一个显示有关学校的信息的屏幕,其中包括所有学生姓名的列表。
- 我想展示一个显示学生信息的屏幕,包括他们的学校名称和他们所修课程的名称。
- 我想缓存此信息,这样我就可以显示同一屏幕而无需等待新的提取。我应该能够从学校到学生再回到学校,而无需再次去学校。
- 我想显示每个屏幕只有一次抓取。从学校页面转到学生页面可以单独获取,但我应该能够在一次获取中显示包含完整学生姓名列表的学校。
- 我想避免重复数据,这样如果学校名称发生变化,一次获取更新学校将导致在学校页面和学生页面上显示正确的名称。
是否有一个好的方法来完成所有这些,或者是否必须取消某些限制?
第一种方法是让 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
对象没有所有参加学生的列表。在数据存储中存储对象的部分表示会导致客户端出现一堆复杂的逻辑。
我们可以存储学校和学生及其嵌套的部分对象,而不是规范化这些对象。然而,这意味着数据重复——Jefferson High 的每个学生都会嵌套学校的名称。如果学校名称在为特定学生进行提取之前发生了变化,那么我们会为该学生显示正确的学校名称,但在其他地方显示错误的名称,包括 "school details" 页面。
另一种方法是将 API 设计为仅 return 嵌套对象的 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]
}
我们总是有 "complete" 对象的表示及其所有引用,因此很容易将此信息存储在数据存储客户端中。但是,这需要多次提取才能显示每个屏幕。要显示有关学生的信息,我们必须获取学生,然后获取他们的学校以及他们的课程。
是否有更智能的方法允许我们只缓存每个对象的一个副本,并防止多次提取以显示基本屏幕?
您可能混淆了两个概念:存储和表示。你可以返回一个非规范化的表示(你建议的第一个选项)而不用也将那些"partial"对象存储在你的数据库中。
所以我建议尝试 return 非标准化表示,但将它们标准化存储(如果您使用的是关系数据库)。
还有一个改进建议:您可能希望在表示中使用正确的 URI 而不是 ID。您可能希望客户端知道 "where" 从中获取该对象,因此只提供 URI 会更容易。否则,客户端需要弄清楚如何从 Id 中生成 URI,而这通常最终会在客户端中进行硬编码,这在 REST 中是不允许的。