假设我有一个典型的用户和组数据模型,其中用户可以在多个组中,一个组可以有许多用户。在我看来,Firebase 文档建议我通过复制组内的用户 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触发器来更新关系的另一方,但触发器尚不可用,将消息发布到外部服务器似乎是一个非常重量级的解决方案,只是为了让该服务器保持冗余数据最新。
所以我正在考虑另一种方法,其中多对多用户组成员身份存储为单独的端点:
{
"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
子项 - 确定她属于哪些组
memberships
- 确定组属性的
groups
子项
在文档中的非规范化示例中,您的代码只需要访问 #1 和 #3,因为成员身份信息嵌入到 users
和 groups
中。
如果进一步非规范化,最终将存储每个用户的所有相关组信息以及每个组的所有相关用户信息。使用这样的数据结构,您只需读取单个位置即可显示组或用户的所有信息。
冗余在NoSQL数据库中不一定是一件坏事,事实上,正是因为它加快了速度。
目前,我将使用一个辅助过程,该过程定期扫描数据并协调它找到的任何不规则数据。当然,这也意味着常规客户端代码需要足够健壮来处理此类不规则数据(例如,指向用户的组,该用户的记录不指向该组)。
或者,您可以设置一些高级.validate
规则,以确保双方始终同步。我总是发现这需要更多的时间来实施,所以从不打扰。
您可能还想阅读此答案:Firebase 数据结构和网址