firebase - Firestore - 适合用户特定数据的 NoSQL 结构
问题描述
我正在使用 Firestore 开发应用程序。我使用 Firebase 身份验证来管理用户。问题是我的应用程序中的大部分数据都是特定于用户的。这意味着用户只能访问自己的数据。
我已经研究过 Firestore 安全规则,所以我知道如何处理安全部分。
但是,我想知道如何正确地为用户特定的数据建模。我要添加的第一条数据是categories
,它(目前)有 2 个属性:name
和type
.
经过一番研究,我发现最好先创建一个名为的集合categories
,然后为每个用户创建一个文档,每个文档都以用户 ID (UID) 命名。
但是,在这样一个特定于用户的文档中,我想添加我的类别。我想出来的一种方法就像下面的屏幕截图一样:
因此,以更通用的方式描述这种方法:
- 每种数据类型的新集合(例如
categories
) - 在集合中,每个用户使用 UID 命名的单独文档
- 在每个用户特定的文档中,带有对象数据的
category_1
地图(例如,带有字段的地图name = groceries
和type = expense
但是,这里让我担心的是,我需要以某种方式发明这些地图的名称,例如category_1
,category_2
等等......我感觉这个模型有问题,但是我强大的 SQL 背景不允许我通过思考
您是否知道这个模型是否是一个好的模型,或者它以后会产生什么问题?也许您可以建议一种更好的方法来在 Firestore 数据库中对特定于用户的数据进行建模?
解决方案
单个用户可以拥有多少个类别有任何限制吗?如果没有,那么最好为类别创建一个集合以避免达到1 MB 最大文档大小。如果有限制并且您决定使用地图,我建议创建一个地图字段categories
并将它们作为子字段,如下所示,因此如果您在文档中添加任何其他字段,它将更加分类:
{
categories: {
category_1: {
name: "",
type: ""
},
category_2: {
name: "",
type: ""
}
}
}
但是,如果每个类别将来获得更多数据并且您需要对类别进行一些查询,那么创建子集合也可能是更好的选择。
users -> {userId} -> categories -> {categoryId}
(col) (doc) (col) (doc)
由于类别位于子集合中,因此您无需在每个类别文档中添加 userId 字段。但是,您也可以创建 2 个不同的根集合,即 - 用户和类别:
users -> {userId}
(col) (doc)
categories -> {categoryId}
(col) (doc)
在这种情况下,您必须存储userId
字段以在这些类别的所有者之间进行过滤。
如果您需要深入的解释,@Alex Mamo在这篇文章中完美地解释了使用根级别集合和子集合之间的区别:在 Firestore 中使用根集合与子集合有什么好处?
在这两种情况下,安全规则会有所不同。如果您使用子集合,那么您可以像这样从通配符中读取用户 UID:
match /users/{userId}/categories/{categoryId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
但是,如果您使用根级别的集合,每个类别的文档 ID 通常不会有用户的 UID,但 UID 将作为字段存储在文档中。在这种情况下,您必须userId
从所请求的文档中阅读。
match /categories/{categoryId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
}
我在这里担心的是我需要以某种方式发明这些地图的名称
NoSQL 数据库没有架构。您可以根据需要动态添加任何键。只需将每个文档视为 JSON 对象。
const newKey = "category_123"
const newValue = {name: "newCatName", type: "expense"}
const userDoc = firebase.firestore().collection("users").doc("userId")
userDoc.update({
[`categories.${newKey}`]: newValue
})
此更新操作将使用提供的值在“类别”地图中创建一个新子项。
推荐阅读
- c# - 如何访问在同一方法中调用其类的方法的对象?
- c# - Unity3d如何使线从相机上的鼠标位置开始
- routing - 如何在 ONE 模拟器(DTN)中制作 hello 数据包?
- javascript - 检查提到的个人资料是否是机器人
- javascript - 在 javascript 函数中创建的对象/元素会发生什么?
- python - Python 代码生成错误数据
- swift - 精灵套件碰撞不起作用
- celery - 带有 Python 3 的 Librabbitmq 2.0.0 给出了 TypeError: can't pickle memoryview objects
- function - Lua 函数参数如何传递?
- c++ - 使用模板参数定义 Hana 结构