我正处于一个十字路口,决定标签应该是他们自己的资源还是笔记的嵌套属性。这个问题涉及 RESTful 设计和数据库存储。
上下文:我有一个笔记资源。用户可以有很多笔记。每个笔记可以有多个标签。
功能目标:我需要创建路由来执行以下操作:
1)获取所有用户标签。像这样:GET /users/:id/tags
2)删除与笔记关联的标签。
3)为特定笔记添加标签。
数据/性能目标
1)获取用户标签应该很快。这是出于"自动建议"/"自动完成"的目的。
2)防止重复(尽可能)。我希望尽可能重复使用标签,以便能够按标签查询数据。例如,我想缓解用户在标签"超级英雄"已存在时键入标签(如"超级英雄")的情况。
话虽如此,在我看来,有两种方法可以在笔记资源上存储标签:
1) 标记作为嵌套属性。例如:
type: 'notes',
attributes: {
id: '123456789',
body: '...',
tags: ['batman', 'superhero']
}
2)标签作为自己的资源。例如:
type: 'notes',
data: {
id: '123456789',
body: '...',
tags: [1,2,3] // <= Tag IDs instead of strings
}
上述任何一种方法都可以工作,但我正在寻找一种允许可扩展性和数据一致性的解决方案(想象一下一百万个注释和一千万个标签)。在这一点上,我倾向于选项 #1,因为它更容易处理代码明智,但不一定是正确的选择。
我很想听听关于不同方法的一些想法,特别是因为我找不到关于这个主题的类似问题。
更新谢谢你的回答。对我来说,最重要的事情之一是确定为什么使用一个而不是另一个是有利的。我希望答案包括一些赞成/反对列表。
tl;博士
考虑到您的要求,IMO 应将tags
存储为资源,并且您的 API 应返回带有标记的notes
作为嵌入属性。
数据库设计
将notes
和tags
保留为单独的集合(或表)。由于您有许多注释和许多标签,并且考虑到核心功能依赖于这些tags
的搜索/自动完成,这将提高搜索特定tags
notes
时的性能。一个非常基本的设计可以看起来像:
笔记
{
'id': 101, // noteid
'title': 'Note title',
'body': 'Some note',
'tags': ['tag1', 'tag2', ...]
}
标签
{
'id': 'tag1', // tagid
'name': 'batman',
'description': 'the dark knight',
'related': ['tagx', 'tagy', ...],
'notes': [101, 103, ...]
}
可以使用related
属性通过替换tagx
、tagy
类似的tags
来处理重复项。
接口设计
1. 获取user
notes
:
GET /users/{userid}/notes
在后端处理此路由时,将tags
嵌入notes
对象中。API 发送的notes
对象应如下所示:
{
'id': 101,
'title': 'Note title',
'body': 'Some note',
'tags': ['batman'] // replacing the tag1 by its name from tag collection
}
2. 获取user
tags
:
GET /users/{userid}/tags
如果不需要,则可以跳过发送包含notes
id
的notes
属性。
3. 删除notes
tags
:
DELETE /users/{userid}/{noteid}/{tag}
4. 为notes
添加tags
:
PUT /users/{userid}/{noteid}/{tag}
解决性能问题,获取tags
user
应该很快,因为您有一个单独的集合。此外,处理重复项会更简单,因为您可以简单地将类似的tags
(按id
或name
)添加到related
数组中。希望这是有帮助的。
为什么不将标记保留为嵌套属性
该设计不像前一种情况那样可扩展。如果
tags
是嵌套属性,并且必须编辑tag
或必须添加某些信息,则需要更改所有notes
,因为多个notes
可以包含相同的tag
。然而,将tags
保留为资源,相同的notes
将与其ids
映射,并且需要在tags
集合/表中进行一次更改。处理重复
tags
可能不像将它们保留为单独的资源那么简单。搜索
tags
时,您需要搜索嵌入在note
中的所有tags
。这会增加开销。
使用tags
作为嵌套属性 IMO 的唯一优点是它可以更轻松地为特定note
添加或删除tags
。
这可能有点复杂。所以我可以分享我在Tag
工作方面的经验(在我们的例子中,这是VoIP应用程序的主要功能)。
在任何情况下,所有Tags
都将作为唯一对象,其中包含大量信息。如您所知,传输会更加复杂,但是您将需要此信息,例如下面。当然,Json 这是最快的解决方案。
type: 'notes',
data: {
id: '123456789',
body: '...',
tags: [UUID1,UUID2,UUID3]
}
例如,您需要多少信息。当您想要根据标签速率、基于数字使用情况的颜色、链接(不相同)、重复项等更改标签的颜色或大小时。
type: 'tag',
data: {
uuid: '234-se-324',
body: 'superhero',
linked: [UUID3, UUID4]
rate: 4.6,
usage: 4323
duplicate: [superheros, suppahero]
}
如您所见,我们甚至使用重复项。只是为了保存每个Tag
的唯一性.当然,我们还包含过滤单词根的逻辑,但正如您从上面的例子中看到的那样,我们还使用带有特殊根的重复值,例如"超级英雄"和"Suppahero",它们对我们来说是相同的。
您可能会认为,这是"自动建议"或"自动完成"的大量信息,但我们从未遇到过性能问题(以防万一,如果服务器方支持理智)。所有信息对于每种用途都很重要,在这种情况下也是如此Note
。
如果要将所有数据放在同一行中,将标记另存为嵌套属性是有意义的。我举个例子。
在发票上添加项目,
标题、描述、价格、数量、税金...
在这种情况下,税收可能是:增值税 20%,因此您用 20% 计算发票,但有一天税收更改为 22%,保存在数据库上的所有发票将增加 2%。在这种情况下,您添加新列并将其保存为原始数字 20,当您从数据库读取该发票时,您会从一行中获取所有数据,而不是从不同的表或变量中计算它。
标签也是如此。如果您以某种方式想要合并重复项,使用 ID 而不是字符串很容易做到。
此外,您还可以考虑其他一些因素。
在社交网络中,用户可能具有称为技能、兴趣、运动等的标签。没有真正的方法来区分标签和(https://github.com/mbleigh/acts-as-taggable-on)
因此,如果您正在制作标签,您将标记许多您必须使用 id
的东西