实现Android+Web(Angular)+Firebase应用,它具有多对多关系:用户<->小部件(小部件可以共享给多个用户)。
考虑:
- 列出用户拥有的所有小部件。
- 用户只能看到共享给他/她的小部件。
- 能够查看共享给定小部件的所有用户。
- 单个 Widget 可以由具有相同权限的多个用户拥有/管理(修改 Widget 并更改其共享对象)。类似于Google云端硬盘与特定用户共享的方式。
实现获取(join-style)的方法之一是遵循以下建议:通过多个侦听器 https://www.firebase.com/docs/android/guide/structuring-data.html(" Joining Flattened Data
")。但是我对这种方法有疑问,因为我发现数据加载速度令人担忧(至少在 Android 上) - 我在另一个问题中问过这个问题 - Firebase Android:使用许多侦听器缓慢"加入",似乎与文档相矛盾。
因此,这个问题是关于另一种方法的:用户拥有的所有小部件的每用户副本。如Firebase+Udacity教程"ShoppingList++"(https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html)中使用的。
它们的结构如下所示:
特别是这部分 - userLists
:
"userLists" : {
"abc@gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz@gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
}
},
"xyz@gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz@gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
},
"-KByb0imU7hFzWTK4eoM" : {
"listName" : "List2",
"owner" : "xyz@gmail,com",
"timestampCreated" : {
"timestamp" : 1457044332539
},
"timestampLastChanged" : {
"timestamp" : 1457044332539
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044332539
}
}
}
},
如您所见,购物清单"Test List 1 Rename 2"
信息的副本显示在两个位置(适用于 2 个用户)。
为了完整起见,这是其余部分:
{
"ownerMappings" : {
"-KBt0MDWbvXFwNvZJXTj" : "xyz@gmail,com",
"-KByb0imU7hFzWTK4eoM" : "xyz@gmail,com"
},
"sharedWith" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"abc@gmail,com" : {
"email" : "abc@gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"shoppingListItems" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"-KBt0heZh-YDWIZNV7xs" : {
"bought" : false,
"itemName" : "item",
"owner" : "xyz@gmail,com"
}
}
},
"uidMappings" : {
"google:112894577549422030859" : "abc@gmail,com",
"google:117151367009479509658" : "xyz@gmail,com"
},
"userFriends" : {
"xyz@gmail,com" : {
"abc@gmail,com" : {
"email" : "abc@gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"users" : {
"abc@gmail,com" : {
"email" : "abc@gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
},
"xyz@gmail,com" : {
"email" : "xyz@gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Karol Depka",
"timestampJoined" : {
"timestamp" : 1456952940258
}
}
}
}
但是,在我开始在我的应用程序中实现类似的结构之前,我想澄清一些疑问。
以下是我相互关联的问题:
- 在他们的ShoppingList++应用程序中,他们只允许在
ownerMappings
节点中分配一个"所有者"。因此,没有其他人可以重命名购物清单。我希望有多个具有平等权利的"所有者"/管理员。这种每用户保留副本的结构是否仍然适用于多个所有者/管理员用户,而不会冒数据损坏/"不同步"或"恶作剧"的风险?
在 - 以下情况下是否会出现数据损坏:用户 1 脱机,将小部件 1 重命名为 小部件 1Prim。当用户 1 脱机时,用户 2 将小部件 1 共享给用户 3(用户 3 的副本还不知道重命名)。User1 联机并发送有关 Widget1 重命名的信息(仅发送到他自己和 User2 的副本,客户端代码在重命名时知道这些副本 - 不更新 User3 的副本)。现在,在一个朴素的实现中,User3 将具有旧名称,而其他将具有新名称。这可能很少见,但仍然有点令人担忧。
- 是否可以/是否应该通过让一些进程(例如在AppEngine上)侦听更改并确保正确传播到所有用户副本来解决"2."点中的数据损坏情况? 和/
- 或是否可以/是否应该通过实现冗余侦听共享和重命名的更改并将更改传播到每用户副本来解决"2."点中的数据损坏情况,以处理特殊情况?大多数情况下,这不是必需的,因此可能会导致性能/带宽损失和复杂的代码。值得吗?
- 展望未来,一旦我们在"野外"部署了多个版本,考虑到客户端中的代码承担了多少数据处理责任,发展模式会不会变得笨拙?例如,如果我们添加一个新关系,而旧的客户端版本还不知道,它看起来不是很脆弱吗?然后,回到例如AppEngine上的服务器端同步器-确保器进程(在问题"3"中描述)?
- 每个小部件/购物清单都有一个"主参考副本",以便为任何会更新每用户副本的同步器-确保器类型的操作提供良好的"事实来源",这似乎是一个好主意吗?
- 关于以这种(冗余)方式结构化的数据的 rules.json/rules.bolt 权限的任何特殊注意事项/陷阱/阻止程序?
PS:我知道通过updateChildren()
进行原子多路径更新 - 肯定会使用它们。
欢迎任何其他提示/观察。蒂亚。
我建议整个系统只有一个小部件的副本。它将有一个源用户 ID 和一组有权访问它的用户。小部件树可以保存用户权限和更改历史记录。每次进行更改时,都会向树中添加一个分支。然后,分支可以像 GIT 一样"提升"为"主"。这将保证数据完整性,因为过去的版本永远不会更改或删除。它还将简化您的获取...我想:)
{
users:[
bob:{
widgets:[
xxx:{
widgetKey: xyz,
permissions: *,
lastEdit...
}
]
}
...
]
widgets:[
xyz:{
masterKey:abc,
data: {...},
owner: bob,
},
...
]
widgetHistory:[
xyz:[
v1:{
data:{...},
},
v2,
v3
]
123:[
...
],
...
]
}