问题描述
实现一个 Android+Web(Angular)+Firebase 应用,它具有多对多的关系:User Widget(Widget 可以共享给多个用户).
Implementing an Android+Web(Angular)+Firebase app, which has a many-to-many relationship: User <-> Widget (Widgets can be shared to multiple users).
注意事项:
- 列出用户拥有的所有小部件.
- 用户只能看到共享给他/她的小部件.
- 能够查看共享给定小部件的所有用户.
- 一个 Widget 可由多个拥有同等权利的用户拥有/管理(修改 Widget 并更改共享对象).类似于 Google 云端硬盘共享给特定用户的方式.
实现获取(join-style)的方法之一是遵循以下建议:https://www.firebase.com/docs/android/guide/structuring-data.html ("Joining Flattened Data
") 通过多个监听器.但是我对这种方法有疑问,因为我发现数据加载会非常缓慢(至少在 Android 上) - 我在另一个问题中询问过它 - Firebase Android:慢速加入";使用许多听众,似乎与文档相矛盾 .
One of the approaches to implement fetching (join-style), would be to go with this advice: https://www.firebase.com/docs/android/guide/structuring-data.html ("Joining Flattened Data
") via multiple listeners.However I have doubts about this approach, because I have discovered that data loading would be worryingly slow (at least on Android) - I asked about it in another question - Firebase Android: slow "join" using many listeners, seems to contradict documentation .
所以,这个问题是关于另一种方法的:用户拥有的所有小部件的每个用户副本.在 Firebase+Udacity 教程ShoppingList++"中使用( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html ).
So, this question is about another approach: per-user copies of all Widgets that a user has. As used in the Firebase+Udacity tutorial "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个用户).
As you can see, the copies of shopping list "Test List 1 Rename 2"
info appears in two places (for 2 users).
这里是完整的其余部分:
And here is the rest for completeness:
{
"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
}
}
}
}
但是,在我开始在我的应用中实现类似的结构之前,我想澄清一些疑问.
However, before I jump into implementing a similar structure in my app, I would like to clarify a few doubts.
这是我的相关问题:
- 在他们的 ShoppingList++ 应用中,他们只允许一个所有者"——在
ownerMappings
节点中分配.因此没有人可以重命名购物清单.我希望拥有多个拥有同等权利的所有者"/管理员.这种按用户保存副本的结构是否仍然适用于多个所有者/管理员用户,而不会冒数据损坏/不同步"或恶作剧"的风险? - 在以下情况下是否会出现数据损坏:User1 离线,将 Widget1 重命名为 Widget1Prim.当 User1 离线时,User2 将 Widget1 共享给 User3(User3 的副本尚不知道重命名).User1 上线并发送有关 Widget1 重命名的信息(仅发送到他自己和 User2 的副本,客户端代码在重命名时知道这一点 - 不更新 User3 的副本).现在,在一个简单的实现中,User3 将使用旧名称,而其他名称将使用新名称.这种情况可能很少见,但仍令人担忧.
- 可能/应该出现第2"点中的数据损坏场景.通过让某些进程(例如在 AppEngine 上)侦听更改并确保正确传播到所有用户副本来解决?
- 和/或可能/应该出现第2"点中的数据损坏场景.通过实施冗余侦听共享和重命名更改并将更改传播到每个用户副本来处理特殊情况来解决?大多数情况下这不是必需的,因此可能会导致性能/带宽损失和代码复杂.值得吗?
- 展望未来,一旦我们在野外"部署了多个版本,考虑到客户端中的代码承担了多少数据处理责任,演化架构会不会变得很笨拙?例如,如果我们添加一个旧客户端版本还不知道的新关系,它看起来是不是很脆弱?然后,回到服务器端的同步器-确保器进程,例如AppEngine(在问题3"中描述)?
- 对于每个小部件/购物清单也有一个主参考副本",以便为任何同步器 - 确保者类型的操作提供良好的真实来源",这似乎是个好主意吗?-用户副本?
- 对于以这种(冗余)方式构建的数据的 rules.json/rules.bolt 权限有任何特殊注意事项/陷阱/阻止程序吗?
- In their ShoppingList++ app, they only permit a single "owner" - assigned in the
ownerMappings
node. Thus no-one else can rename the shopping list. I would like to have multiple "owners"/admins, with equal rights. Would such a keep-copies-per-user structure still work for multiple owner/admin users, without risking data corruption/"desynchronization" or "pranks"? - Could data corruption arise in scenarios like this: User1 goes offline, renames Widget1 to Widget1Prim. While User1 is offline, User2 shares Widget1 to User3 (User3's copy would not yet be aware of the rename). User1 goes online and sends the info about the rename of Widget1 (only to his own and User2's copies, of which the client code was aware at the time of the rename - not updating User3's copy). Now, in a naive implementation, User3 would have the old name, while the others would have the new name. This would probably be rare, but still worrying a bit.
- Could/should the data corruption scenario in point "2." be resolved via having some process (e.g. on AppEngine) listening to changes and ensuring proper propagation to all user copies?
- And/or could/should the data corruption scenario in point "2." be resolved via implementing a redundant listening to both changes of sharing and renaming, and propagating the changes to per-user copies, to handle the special case? Most of the time this would not be necessary, so it could result in performance/bandwidth penalty and complicated code. Is it worth it?
- Going forward, once we have multiple versions deployed "in the wild", wouldn't it become unwieldy to evolve the schema, given how much of the data-handling responsibility lies with the code in the clients? For example if we add a new relationship, that the older client versions don't yet know about, doesn't it seem fragile? Then, back to the server-side syncer-ensurerer process on e.g. AppEngine (described in question "3.") ?
- Would it seem like a good idea, to also have a "master reference copy" of every Widget / shopping-list, so as to give good "source of truth" for any syncer-ensurerer type of operations that would update per-user copies?
- Any special considerations/traps/blockers regarding rules.json / rules.bolt permissions for data structured in such a (redundant) way ?
PS:我通过 updateChildren()
了解原子多路径更新 - 肯定会使用它们.
PS: I know about atomic multi-path updates via updateChildren()
- would definitely use them.
欢迎任何其他提示/观察.TIA.
Any other hints/observations welcome. TIA.
推荐答案
我建议整个系统只有一个小部件的副本.它将有一个原始用户 ID,以及一组有权访问它的用户.小部件树可以保存用户权限和更改历史记录.每当进行更改时,都会向树中添加一个分支.然后可以将分支提升"为类似于 GIT 的主".这将保证数据完整性,因为过去的版本永远不会被更改或删除.它还可以简化您的提取...我认为 :)
I suggest having only one copy of a widget for the entire system. It would have an origin user ID, and a set of users that have access to it. The widget tree can hold user permissions and change history. Any time a change is made, a branch is added to the tree. Branches can then be "promoted" to the "master" kind of like GIT. This would guarantee data integrity because past versions are never changed or deleted. It would also simplify your fetches... I think :)
{
users:[
bob:{
widgets:[
xxx:{
widgetKey: xyz,
permissions: *,
lastEdit...
}
]
}
...
]
widgets:[
xyz:{
masterKey:abc,
data: {...},
owner: bob,
},
...
]
widgetHistory:[
xyz:[
v1:{
data:{...},
},
v2,
v3
]
123:[
...
],
...
]
}
这篇关于Firebase:通过每个用户的副本构建数据?数据损坏风险?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!