在 firebase 中,使用单独的端点对多对多关系建模是个好主意吗?
In firebase, is modeling many-many relationships using a separate endpoint a good idea?
假设我有一个典型的用户和组数据模型,其中一个用户可以在多个组中,一个组可以有多个用户。在我看来,firebase docs 建议我通过复制组内的用户 ID 和用户内的组 ID 来建模我的数据,如下所示:
{
"usergroups": {
"bob": {
"groups": {
"one": true,
"two": true
}
},
"fred": {
"groups": {
"one": true
}
}
},
"groupusers": {
"one": {
"users": {
"bob": true,
"fred": true
}
},
"two": {
"users": {
"bob": true
}
}
}
}
为了保持这种结构,每当我的应用程序更新关系的一侧(例如,将用户添加到组)时,它也需要更新关系的另一侧(例如,将组添加到用户)。
我担心最终某人的计算机会在更新过程中崩溃或出现其他问题,并且关系的两端会不同步。理想情况下,我想将更新放在一个事务中,这样要么双方都得到更新,要么双方都没有,但据我所知,我不能用 firebase 中的当前事务支持来做到这一点。
另一种方法是使用即将推出的 firebase 触发器来更新关系的另一端,但触发器尚不可用,这似乎是 post 向外部服务器发送消息的非常重量级的解决方案只是为了让该服务器保持冗余数据最新。
所以我正在考虑另一种方法,其中许多用户组成员资格存储为单独的端点:
{
"memberships": {
"id1": {
"user": "bob",
"group": "one"
},
"id2": {
"user": "bob",
"group": "two"
},
"id3": {
"user": "fred",
"group": "one"
}
}
}
我可以在 "user" 和 "group" 上添加索引,并发出 firebase 查询“.orderByChild("user").equalTo(...)”和“.orderByChild("group").equalTo(...)" 分别确定特定用户的组和特定组的用户。
这种方法有什么缺点?我们不再需要维护冗余数据,那么为什么这不是推荐的方法呢?它是否比推荐的复制数据方法慢得多?
在您建议的设计中,您始终需要访问三个位置来显示用户及其组:
users
child判断用户的属性
memberships
以确定她是哪个组的成员
groups
child判断组的属性
在文档中的非规范化示例中,您的代码只需要访问#1 和#3,因为成员信息已嵌入到 users
和 groups
中。
如果进一步反规范化,您最终会存储每个用户的所有相关组信息以及每个组的所有相关用户信息。使用这样的数据结构,您只需读取一个位置即可显示组或用户的所有信息。
冗余在 NoSQL 数据库中不一定是坏事,事实上正是因为它加快了速度。
目前我会使用辅助进程,该进程会定期扫描数据并协调它发现的任何不规则数据。当然,这也意味着常规客户端代码需要足够健壮以处理此类不规则数据(例如,指向用户的组,而该用户的记录不指向该组)。
或者,您可以设置一些高级 .validate
规则来确保双方始终保持同步。我总是发现实施起来需要更多时间,所以从来没有打扰过。
您可能还想阅读这个答案:Firebase data structure and url
假设我有一个典型的用户和组数据模型,其中一个用户可以在多个组中,一个组可以有多个用户。在我看来,firebase docs 建议我通过复制组内的用户 ID 和用户内的组 ID 来建模我的数据,如下所示:
{
"usergroups": {
"bob": {
"groups": {
"one": true,
"two": true
}
},
"fred": {
"groups": {
"one": true
}
}
},
"groupusers": {
"one": {
"users": {
"bob": true,
"fred": true
}
},
"two": {
"users": {
"bob": true
}
}
}
}
为了保持这种结构,每当我的应用程序更新关系的一侧(例如,将用户添加到组)时,它也需要更新关系的另一侧(例如,将组添加到用户)。
我担心最终某人的计算机会在更新过程中崩溃或出现其他问题,并且关系的两端会不同步。理想情况下,我想将更新放在一个事务中,这样要么双方都得到更新,要么双方都没有,但据我所知,我不能用 firebase 中的当前事务支持来做到这一点。
另一种方法是使用即将推出的 firebase 触发器来更新关系的另一端,但触发器尚不可用,这似乎是 post 向外部服务器发送消息的非常重量级的解决方案只是为了让该服务器保持冗余数据最新。
所以我正在考虑另一种方法,其中许多用户组成员资格存储为单独的端点:
{
"memberships": {
"id1": {
"user": "bob",
"group": "one"
},
"id2": {
"user": "bob",
"group": "two"
},
"id3": {
"user": "fred",
"group": "one"
}
}
}
我可以在 "user" 和 "group" 上添加索引,并发出 firebase 查询“.orderByChild("user").equalTo(...)”和“.orderByChild("group").equalTo(...)" 分别确定特定用户的组和特定组的用户。
这种方法有什么缺点?我们不再需要维护冗余数据,那么为什么这不是推荐的方法呢?它是否比推荐的复制数据方法慢得多?
在您建议的设计中,您始终需要访问三个位置来显示用户及其组:
users
child判断用户的属性memberships
以确定她是哪个组的成员groups
child判断组的属性
在文档中的非规范化示例中,您的代码只需要访问#1 和#3,因为成员信息已嵌入到 users
和 groups
中。
如果进一步反规范化,您最终会存储每个用户的所有相关组信息以及每个组的所有相关用户信息。使用这样的数据结构,您只需读取一个位置即可显示组或用户的所有信息。
冗余在 NoSQL 数据库中不一定是坏事,事实上正是因为它加快了速度。
目前我会使用辅助进程,该进程会定期扫描数据并协调它发现的任何不规则数据。当然,这也意味着常规客户端代码需要足够健壮以处理此类不规则数据(例如,指向用户的组,而该用户的记录不指向该组)。
或者,您可以设置一些高级 .validate
规则来确保双方始终保持同步。我总是发现实施起来需要更多时间,所以从来没有打扰过。
您可能还想阅读这个答案:Firebase data structure and url